From 1e1fcdf08dc462b01de09218a761d22d7c3b07af Mon Sep 17 00:00:00 2001 From: "karl@kubx.ca" Date: Fri, 10 Nov 2017 09:09:38 -0500 Subject: [PATCH 0001/1825] Remove obsolete Input interface --- .../src/main/java/org/tensorflow/Input.java | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 tensorflow/java/src/main/java/org/tensorflow/Input.java diff --git a/tensorflow/java/src/main/java/org/tensorflow/Input.java b/tensorflow/java/src/main/java/org/tensorflow/Input.java deleted file mode 100644 index 13bc463e7d..0000000000 --- a/tensorflow/java/src/main/java/org/tensorflow/Input.java +++ /dev/null @@ -1,48 +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. -==============================================================================*/ - -package org.tensorflow; - -/** - * Interface implemented by operands of a TensorFlow operation. - * - *

Example usage: - * - *

{@code
- * // The "decodeJpeg" operation can be used as input to the "cast" operation
- * Input decodeJpeg = ops.image().decodeJpeg(...);
- * ops.math().cast(decodeJpeg, DataType.FLOAT);
- *
- * // The output "y" of the "unique" operation can be used as input to the "cast" operation
- * Output y = ops.array().unique(...).y();
- * ops.math().cast(y, DataType.FLOAT);
- *
- * // The "split" operation can be used as input list to the "concat" operation
- * Iterable split = ops.array().split(...);
- * ops.array().concat(0, split);
- * }
- */ -public interface Input { - - /** - * Returns the symbolic handle of a tensor. - * - *

Inputs to TensorFlow operations are outputs of another TensorFlow operation. This method is - * used to obtain a symbolic handle that represents the computation of the input. - * - * @see OperationBuilder#addInput(Output) - */ - Output asOutput(); -} -- GitLab From 38790bb1beb6b00b4286c9cfff93c8d0834395fe Mon Sep 17 00:00:00 2001 From: Codrut Grosu Date: Sun, 10 Dec 2017 11:46:12 +0200 Subject: [PATCH 0002/1825] Minor optimization of conv_ops. If data_format is NCHW, and the computation is done on GPU, the temporary tensor transformed_output is not needed. One can use directly the output tensor. This leaves more memory for the scratch allocator, possibly improving performance. I also fixed several style issues in conv_ops.cc reported by clang-format. --- tensorflow/core/kernels/conv_ops.cc | 44 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/tensorflow/core/kernels/conv_ops.cc b/tensorflow/core/kernels/conv_ops.cc index ba40c428e4..e16895413e 100644 --- a/tensorflow/core/kernels/conv_ops.cc +++ b/tensorflow/core/kernels/conv_ops.cc @@ -315,19 +315,18 @@ class Conv2DOp : public BinaryOp { 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")); } // The last dimension for input is in_depth. It must be the same as the // filter's in_depth. const int64 in_depth = GetTensorDim(input, data_format_, 'C'); - OP_REQUIRES(context, in_depth == filter.dim_size(2), - errors::InvalidArgument( - "input and filter must have the same depth: ", in_depth, - " vs ", filter.dim_size(2))); + OP_REQUIRES( + context, in_depth == filter.dim_size(2), + errors::InvalidArgument("input and filter must have the same depth: ", + in_depth, " vs ", filter.dim_size(2))); // The last dimension for filter is out_depth. const int out_depth = static_cast(filter.dim_size(3)); @@ -335,20 +334,18 @@ class Conv2DOp : public BinaryOp { // The second dimension for input is rows/height. // The first dimension for filter is rows/height. const int64 input_rows_raw = 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)); // The third dimension for input is columns/width. // The second dimension for filter is columns/width. const int64 input_cols_raw = 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)); @@ -663,11 +660,14 @@ void LaunchConv2DOp::operator()( To32Bit(transformed_filter.tensor())); Tensor transformed_output; - OP_REQUIRES_OK( - ctx, ctx->allocate_temp(DataTypeToEnum::value, - ShapeFromFormat(FORMAT_NCHW, out_batch, out_rows, - out_cols, out_depths), - &transformed_output)); + if (data_format == FORMAT_NHWC) + OP_REQUIRES_OK( + ctx, ctx->allocate_temp(DataTypeToEnum::value, + ShapeFromFormat(FORMAT_NCHW, out_batch, + out_rows, out_cols, out_depths), + &transformed_output)); + else + transformed_output = *output; auto input_ptr = AsDeviceMemory(input.template flat().data(), input.template flat().size()); @@ -772,8 +772,6 @@ void LaunchConv2DOp::operator()( ctx->eigen_device(), const_cast(transformed_output).tensor(), output->tensor()); - } else { - *output = transformed_output; } } -- GitLab From 5d7ecc17e3627529b1bf1c732645ca7f457cda89 Mon Sep 17 00:00:00 2001 From: pillarpond Date: Sat, 12 May 2018 00:31:09 +0900 Subject: [PATCH 0003/1825] Use AvailableArrayName for sanity --- tensorflow/contrib/lite/toco/import_tensorflow.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/lite/toco/import_tensorflow.cc b/tensorflow/contrib/lite/toco/import_tensorflow.cc index 1eef173afe..9ed9d4a6cf 100644 --- a/tensorflow/contrib/lite/toco/import_tensorflow.cc +++ b/tensorflow/contrib/lite/toco/import_tensorflow.cc @@ -454,7 +454,8 @@ void ConvertConvOperator(const NodeDef& node, const auto& input_name = node.input(0); const auto& weights_name = node.input(1); - const auto& reordered_weights_name = weights_name + "_reordered"; + const auto& reordered_weights_name = + AvailableArrayName(*model, weights_name + "_reordered"); // Check if a ReorderAxesOperator was already created for these weights // (that happens when multiple layers share the same weights). const Operator* existing_reorder = -- GitLab From 3f8994224be02e347517ccade01ad92d5c6c90e3 Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Fri, 29 Jun 2018 13:03:10 +0100 Subject: [PATCH 0004/1825] BindToDevice() bind graph to specified (gpu) device which forces all its operations to be prcessed on that device. Export to golang as well. --- tensorflow/c/c_api.cc | 9 +++++++++ tensorflow/c/c_api.h | 2 ++ tensorflow/go/graph.go | 17 ++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc index a8ad8e4b94..7ae9c9bf28 100644 --- a/tensorflow/c/c_api.cc +++ b/tensorflow/c/c_api.cc @@ -2118,6 +2118,15 @@ void TF_GraphImportGraphDef(TF_Graph* graph, const TF_Buffer* graph_def, TF_DeleteImportGraphDefResults(results); } +void TF_BindToDevice(TF_Graph* graph, const char *device) { + if (device == NULL) + return; + + for (Node *node: graph->graph.nodes()) { + node->set_requested_device(device); + } +} + // While loop functions ------------------------------------------------------- namespace { diff --git a/tensorflow/c/c_api.h b/tensorflow/c/c_api.h index 1eb75ef11f..e9aaeb0b81 100644 --- a/tensorflow/c/c_api.h +++ b/tensorflow/c/c_api.h @@ -1018,6 +1018,8 @@ TF_CAPI_EXPORT extern void TF_GraphImportGraphDef( TF_Graph* graph, const TF_Buffer* graph_def, const TF_ImportGraphDefOptions* options, TF_Status* status); +TF_CAPI_EXPORT extern void TF_BindToDevice(TF_Graph* graph, const char *device); + // Adds a copy of function `func` and optionally its gradient function `grad` // to `g`. Once `func`/`grad` is added to `g`, it can be called by creating // an operation using the function's name. diff --git a/tensorflow/go/graph.go b/tensorflow/go/graph.go index 08943a527c..5b274501f8 100644 --- a/tensorflow/go/graph.go +++ b/tensorflow/go/graph.go @@ -92,7 +92,7 @@ func (g *Graph) WriteTo(w io.Writer) (int64, error) { // another Graph into g. // // Names of imported nodes will be prefixed with prefix. -func (g *Graph) Import(def []byte, prefix string) error { +func (g *Graph) ImportWithDevice(def []byte, prefix string, device string) error { cprefix := C.CString(prefix) defer C.free(unsafe.Pointer(cprefix)) @@ -118,9 +118,24 @@ func (g *Graph) Import(def []byte, prefix string) error { if err := status.Err(); err != nil { return err } + + g.BindToDevice(device) return nil } +func (g *Graph) BindToDevice(device string) { + if len(device) != 0 { + cdev := C.CString(device) + defer C.free(unsafe.Pointer(cdev)) + + C.TF_BindToDevice(g.c, cdev) + } +} + +func (g *Graph) Import(def []byte, prefix string) error { + return g.ImportWithDevice(def, prefix, "") +} + // Operation returns the Operation named name in the Graph, or nil if no such // operation is present. func (g *Graph) Operation(name string) *Operation { -- GitLab From fe3bd66099d6e27bf45dabbe7f4cc34276194fc3 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: Wed, 1 Aug 2018 13:37:26 +0800 Subject: [PATCH 0005/1825] ENH: register float64 GPU kernel for Conv3d --- tensorflow/core/kernels/conv_ops_3d.cc | 14 +++++++++++++- tensorflow/core/kernels/conv_ops_gpu_3.cu.cc | 5 +++++ tensorflow/python/kernel_tests/conv_ops_3d_test.py | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/kernels/conv_ops_3d.cc b/tensorflow/core/kernels/conv_ops_3d.cc index a1eed4e68c..009fd3733a 100644 --- a/tensorflow/core/kernels/conv_ops_3d.cc +++ b/tensorflow/core/kernels/conv_ops_3d.cc @@ -525,10 +525,19 @@ namespace functor { const GPUDevice& d, typename TTypes::ConstTensor in, \ const std::array& padding_left, \ const std::array& padding_right, \ - typename TTypes::Tensor out, TensorFormat format); + typename TTypes::Tensor out, TensorFormat format); \ + template <> \ + void NHWCToNCHW::operator()( \ + const GPUDevice& d, typename TTypes::ConstTensor in, \ + typename TTypes::Tensor out); \ + template <> \ + void NCHWToNHWC::operator()( \ + const GPUDevice& d, typename TTypes::ConstTensor in, \ + typename TTypes::Tensor out); DECLARE_GPU_SPEC(Eigen::half); DECLARE_GPU_SPEC(float); +DECLARE_GPU_SPEC(double); #undef DECLARE_GPU_SPEC } // namespace functor @@ -540,6 +549,9 @@ REGISTER_KERNEL_BUILDER( REGISTER_KERNEL_BUILDER( Name("Conv3D").Device(DEVICE_GPU).TypeConstraint("T"), Conv3DOp); +REGISTER_KERNEL_BUILDER( + Name("Conv3D").Device(DEVICE_GPU).TypeConstraint("T"), + Conv3DOp); #endif // GOOGLE_CUDA } // namespace tensorflow diff --git a/tensorflow/core/kernels/conv_ops_gpu_3.cu.cc b/tensorflow/core/kernels/conv_ops_gpu_3.cu.cc index a5fa48f85e..f8520cc307 100644 --- a/tensorflow/core/kernels/conv_ops_gpu_3.cu.cc +++ b/tensorflow/core/kernels/conv_ops_gpu_3.cu.cc @@ -1068,18 +1068,23 @@ template struct functor::PadInput; template struct functor::PadInput; // For 3d ops. +template struct functor::TransformFilter; template struct functor::TransformFilter; template struct functor::TransformFilter; +template struct functor::ReverseTransformFilter; template struct functor::ReverseTransformFilter; template struct functor::ReverseTransformFilter; +template struct functor::NHWCToNCHW; template struct functor::NHWCToNCHW; template struct functor::NHWCToNCHW; +template struct functor::NCHWToNHWC; template struct functor::NCHWToNHWC; template struct functor::NCHWToNHWC; +template struct functor::PadInput; template struct functor::PadInput; template struct functor::PadInput; diff --git a/tensorflow/python/kernel_tests/conv_ops_3d_test.py b/tensorflow/python/kernel_tests/conv_ops_3d_test.py index 0b531125f3..7a0f51dfef 100644 --- a/tensorflow/python/kernel_tests/conv_ops_3d_test.py +++ b/tensorflow/python/kernel_tests/conv_ops_3d_test.py @@ -52,11 +52,11 @@ class Conv3DTest(test.TestCase): def _DtypesToTest(self, use_gpu): if use_gpu: if not test_util.CudaSupportsHalfMatMulAndConv(): - return [dtypes.float32] + return [dtypes.float64, dtypes.float32] else: # It is important that float32 comes before float16 here, # as we will be using its gradients as reference for fp16 gradients. - return [dtypes.float32, dtypes.float16] + return [dtypes.float64, dtypes.float32, dtypes.float16] else: return [dtypes.float64, dtypes.float32, dtypes.float16] -- GitLab From 9d45b84c12c8c9fb7a928adb9affaa91b35c7e2b Mon Sep 17 00:00:00 2001 From: Mahmoud Abuzaina Date: Mon, 13 Aug 2018 18:04:38 -0700 Subject: [PATCH 0006/1825] Ran clang-format tool --- tensorflow/tools/graph_transforms/BUILD | 1 + .../fuse_quantized_convolution.cc | 220 +++++++++++ .../tools/quantization/quantize_graph.py | 349 +++++++++++++++++- 3 files changed, 557 insertions(+), 13 deletions(-) create mode 100644 tensorflow/tools/graph_transforms/fuse_quantized_convolution.cc diff --git a/tensorflow/tools/graph_transforms/BUILD b/tensorflow/tools/graph_transforms/BUILD index 1ad1895269..eb1ed1f2ca 100644 --- a/tensorflow/tools/graph_transforms/BUILD +++ b/tensorflow/tools/graph_transforms/BUILD @@ -97,6 +97,7 @@ cc_library( "fold_old_batch_norms.cc", "freeze_requantization_ranges.cc", "fuse_convolutions.cc", + "fuse_quantized_convolution.cc", "insert_logging.cc", "obfuscate_names.cc", "quantize_nodes.cc", diff --git a/tensorflow/tools/graph_transforms/fuse_quantized_convolution.cc b/tensorflow/tools/graph_transforms/fuse_quantized_convolution.cc new file mode 100644 index 0000000000..2128bcd978 --- /dev/null +++ b/tensorflow/tools/graph_transforms/fuse_quantized_convolution.cc @@ -0,0 +1,220 @@ +/* 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. +==============================================================================*/ +#ifdef INTEL_MKL +#include + +#include "tensorflow/core/common_runtime/constant_folding.h" +#include "tensorflow/core/framework/attr_value.pb.h" +#include "tensorflow/core/framework/node_def_util.h" +#include "tensorflow/core/framework/numeric_types.h" +#include "tensorflow/core/graph/graph_constructor.h" +#include "tensorflow/core/graph/node_builder.h" +#include "tensorflow/core/graph/subgraph.h" +#include "tensorflow/core/lib/strings/str_util.h" +#include "tensorflow/core/platform/init_main.h" +#include "tensorflow/core/public/session.h" +#include "tensorflow/core/util/command_line_flags.h" +#include "tensorflow/tools/graph_transforms/fold_constants_lib.h" +#include "tensorflow/tools/graph_transforms/transform_utils.h" + +namespace tensorflow { +namespace graph_transforms { + +Status FuseQuantizedConvolutionAndRequantize( + const GraphDef& input_graph_def, const TransformFuncContext& context, + GraphDef* output_graph_def) { + std::map node_map; + MapNamesToNodes(input_graph_def, &node_map); + GraphDef replaced_graph_def; + TF_RETURN_IF_ERROR(ReplaceMatchingOpTypes( + input_graph_def, // clang-format off + + {"Requantize", + { + {"QuantizedConv2D|QuantizedConv2DWithBias|QuantizedConv2DWithRelu|" + "QuantizedConv2DWithBiasAndRelu|QuantizedConv2DWithBiasSumAndRelu"}, + {"QuantizedConv2D|QuantizedConv2DWithBias|QuantizedConv2DWithRelu|" + "QuantizedConv2DWithBiasAndRelu|QuantizedConv2DWithBiasSumAndRelu"}, + {"QuantizedConv2D|QuantizedConv2DWithBias|QuantizedConv2DWithRelu|" + "QuantizedConv2DWithBiasAndRelu|QuantizedConv2DWithBiasSumAndRelu"}, + {"Const"}, + {"Const"} + } + }, // clang-format on */ + [&node_map](const NodeMatch& match, const std::set& input_nodes, + const std::set& output_nodes, + std::vector* new_nodes) { + // TODO(mdfaijul/sheng): Current implementation assumed all + // requantization cases have bias. Index of inputs need to be updated + // for non-bias cases. + + // Find all the nodes we expect in the subgraph. + const NodeDef& requantize_node = match.node; + CHECK_EQ("Requantize", requantize_node.op()); + const NodeDef& quantized_conv2D_node = match.inputs[0].node; + const NodeDef& const_requantize_range_min_node = match.inputs[3].node; + CHECK_EQ("Const", const_requantize_range_min_node.op()); + const NodeDef& const_requantize_range_max_node = match.inputs[4].node; + CHECK_EQ("Const", const_requantize_range_max_node.op()); + + string quantized_conv2D_op_name = quantized_conv2D_node.op(); + // Set up the new fused version of the convolution op. + NodeDef fused_conv; + fused_conv.set_op(quantized_conv2D_op_name + "AndRequantize"); + fused_conv.set_name(match.node.name()); + int n_input = quantized_conv2D_node.input_size(); + if (quantized_conv2D_op_name.compare( + "QuantizedConv2DWithBiasSumAndRelu") == 0) + n_input -= 1; // -1 since summand is moved after frozen min-max + + for (int i=0; i < n_input; i++) + AddNodeInput(quantized_conv2D_node.input(i), &fused_conv); + + AddNodeInput(const_requantize_range_min_node.name(), &fused_conv); + AddNodeInput(const_requantize_range_max_node.name(), &fused_conv); + + // Add additional inputs to + // QuantizedConv2DWithBiasSumAndReluAndRequantize + if (quantized_conv2D_op_name.compare( + "QuantizedConv2DWithBiasSumAndRelu") == 0) { + const NodeDef *in_requantize = node_map[node_map[ + quantized_conv2D_node.input(n_input)]->input(0)]; + string summand(in_requantize->name()); + string min_summand(in_requantize->name() + ":1"); + string max_summand(in_requantize->name() + ":2"); + AddNodeInput(summand, &fused_conv); + AddNodeInput(min_summand, &fused_conv); + AddNodeInput(max_summand, &fused_conv); + + // Signed version QuantizedConv2DWithBiasSumAndReluAndRequantize + // if Relu does not follow the convolution operation + std::vector signed_ops = { + "QuantizedConv2DWithBias", + "QuantizedConv2D" + }; + bool is_signed_summand = + std::find(signed_ops.begin(), signed_ops.end(), + node_map[in_requantize->input(0)]->op()) != signed_ops.end(); + if (is_signed_summand) { + fused_conv.set_op( + "QuantizedConv2DWithBiasSignedSumAndReluAndRequantize"); + SetNodeAttr("Tsummand", DT_QINT8, &fused_conv); + } else { + SetNodeAttr("Tsummand", DT_QUINT8, &fused_conv); + } + } + CopyNodeAttr(quantized_conv2D_node, "Tinput", "Tinput", &fused_conv); + CopyNodeAttr(quantized_conv2D_node, "Tfilter", "Tfilter", &fused_conv); + CopyNodeAttr(quantized_conv2D_node, "strides", "strides", &fused_conv); + CopyNodeAttr(quantized_conv2D_node, "padding", "padding", &fused_conv); + + // Copy dilation attribute if exsit in the orginal node + if (HasNodeAttr(quantized_conv2D_node, "dilations")) + CopyNodeAttr(quantized_conv2D_node, "dilations", + "dilations", &fused_conv); + if (quantized_conv2D_op_name.compare("QuantizedConv2D") == 0 || + quantized_conv2D_op_name.compare("QuantizedConv2DWithBias") == 0) + SetNodeAttr("out_type", DT_QINT8, &fused_conv); + else + SetNodeAttr("out_type", DT_QUINT8, &fused_conv); + new_nodes->push_back(fused_conv); + new_nodes->push_back(const_requantize_range_min_node); + new_nodes->push_back(const_requantize_range_max_node); + + return Status::OK(); + }, + {}, &replaced_graph_def)); + + // Convert bias float -> int32 on replaced_graph_def + std::vector fused_requantized_bias_ops = { + "QuantizedConv2DWithBiasAndRequantize", + "QuantizedConv2DWithBiasAndReluAndRequantize", + "QuantizedConv2DWithBiasSumAndReluAndRequantize", + "QuantizedConv2DWithBiasSignedSumAndReluAndRequantize" + }; + node_map.clear(); + MapNamesToNodes(replaced_graph_def, &node_map); + for (auto& node_pair : node_map) { + const NodeDef *node = node_pair.second; + bool is_fused_requantized_conv_op = + std::find(fused_requantized_bias_ops.begin(), + fused_requantized_bias_ops.end(), + node->op()) != fused_requantized_bias_ops.end(); + if (is_fused_requantized_conv_op) { + // If the op is not fed by Another Requantize op, + // then we coonvert bias as Int32 + string input_op = node_map[NodeNameFromInput(node->input(0))]->op(); + if (str_util::StartsWith(input_op, "QuantizedConv2D") && + str_util::EndsWith(input_op, "AndRequantize")) { + NodeDef *bias_node = const_cast(node_map[NodeNameFromInput( + node->input(2))]); + const NodeDef *min_input_node = node_map[NodeNameFromInput( + node_map[node->input(0)]->input(7))]; + const NodeDef *max_input_node = node_map[NodeNameFromInput( + node_map[node->input(0)]->input(8))]; + const NodeDef *min_filter_node = node_map[NodeNameFromInput( + node->input(5))]; + const NodeDef *max_filter_node = node_map[NodeNameFromInput( + node->input(6))]; + const float min_input = + GetNodeTensorAttr(*min_input_node, "value").flat()(0); + const float max_input = + GetNodeTensorAttr(*max_input_node, "value").flat()(0); + const float min_filter = + GetNodeTensorAttr(*min_filter_node, "value").flat()(0); + const float max_filter = + GetNodeTensorAttr(*max_filter_node, "value").flat()(0); + + TensorProto float_tensor_proto = bias_node->attr().at("value").tensor(); + Tensor float_tensor; + CHECK(float_tensor.FromProto(float_tensor_proto)); + CHECK_EQ(float_tensor.dtype(), DT_FLOAT); + float *p_bias_float = float_tensor.flat().data(); + + Tensor int32_tensor = Tensor(DT_QINT32, float_tensor.shape()); + qint32 *p_bias_int32 = int32_tensor.flat().data(); + + float bias_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))); + int64 nelems = float_tensor.NumElements(); + for (int64 n = 0; n < nelems; n++) + p_bias_int32[n] = (int32_t) (p_bias_float[n] * bias_scale); + + bias_node->clear_attr(); + AttrValue attr_type; + attr_type.set_type(int32_tensor.dtype()); + bias_node->mutable_attr()->insert({"dtype", attr_type}); + + AttrValue attr_tensor; + TensorProto* t = attr_tensor.mutable_tensor(); + int32_tensor.AsProtoTensorContent(t); + bias_node->mutable_attr()->insert({"value", attr_tensor}); + SetNodeAttr("Tbias", DT_QINT32, const_cast(node)); + } else { + SetNodeAttr("Tbias", DT_FLOAT, const_cast(node)); + } + } + } + *output_graph_def = replaced_graph_def; + return Status::OK(); +} + +REGISTER_GRAPH_TRANSFORM("fuse_quantized_conv_and_requantize", + FuseQuantizedConvolutionAndRequantize); + +} // namespace graph_transforms +} // namespace tensorflow +#endif // INTEL_MKL diff --git a/tensorflow/tools/quantization/quantize_graph.py b/tensorflow/tools/quantization/quantize_graph.py index 3acb532263..14b572c15f 100644 --- a/tensorflow/tools/quantization/quantize_graph.py +++ b/tensorflow/tools/quantization/quantize_graph.py @@ -21,6 +21,7 @@ bazel build tensorflow/tools/quantization:quantize_graph \ --output_node_names="softmax2" --print_nodes --output=/tmp/quantized_graph.pb \ --mode=eightbit --logtostderr +To quantize for Intel CPU, add --intel_cpu_eightbitize=True. """ from __future__ import absolute_import @@ -46,6 +47,7 @@ from tensorflow.python.ops import array_ops from tensorflow.python.platform import app from tensorflow.python.platform import flags as flags_lib from tensorflow.python.platform import gfile +from google.protobuf import text_format flags = flags_lib FLAGS = flags.FLAGS @@ -87,7 +89,14 @@ flags.DEFINE_float( "information. Note: this should be considered a coarse tool just good " "enough for experimentation purposes, since graphs quantized in this way " "would be very inaccurate.") - +flags.DEFINE_boolean("input_binary", True, + """Input graph binary or text.""") +flags.DEFINE_boolean("output_binary", True, + """Output graph binary or text.""") +flags.DEFINE_boolean( + "intel_cpu_eightbitize", False, + "If true eightbitized graph will include fused quantized" + "nodes in the output_graph for Intel CPU.") def print_input_nodes(current_node, nodes_map, indent, already_visited): print(" " * indent + current_node.op + ":" + current_node.name) @@ -297,6 +306,8 @@ def quantize_weight_eightbit(input_node, quantization_mode): dtypes.quint8, mode=quantization_mode) quint8_tensor = quantize_op[0].eval() + min_value = quantize_op[1].eval() + max_value = quantize_op[2].eval() shape = tensor_util.TensorShapeProtoToList(input_node.attr["value"] .tensor.tensor_shape) quint8_const_node = create_constant_node( @@ -309,6 +320,57 @@ def quantize_weight_eightbit(input_node, quantization_mode): set_attr_string(dequantize_node, "mode", quantization_mode) return [quint8_const_node, min_node, max_node, dequantize_node] +# TODO(intel-tf): Current Intel-CPU quantized Conv2D and Matmul supports only +# signed scaled mode of weight quantization. +def intel_cpu_quantize_weight_eightbit(input_node, quantization_mode="SCALED"): + """Returns replacement of constant weight node. + + This function creates (i) a quantized constant node, (ii) a float min node + (iii) a float max node, and (iv) a dequantize node.""" + base_name = input_node.name + "_" + qint8_const_name = base_name + "qint8_const" + min_name = base_name + "min" + max_name = base_name + "max" + float_tensor = tensor_util.MakeNdarray(input_node.attr["value"].tensor) + min_value = np.min(float_tensor.flatten()) + max_value = np.max(float_tensor.flatten()) + # Same processing of min-max as in quantize_weight_eightbit function. + if min_value > 0.0: + min_value = 0.0 + if min_value == max_value: + if abs(min_value) < 0.000001: + max_value = min_value + 1.0 + elif min_value > 0: + max_value = 2 * min_value + else: + max_value = min_value / 2.0 + + sess = session.Session() + with sess.as_default(): + quantize_op = array_ops.quantize_v2( + float_tensor, + min_value, + max_value, + dtypes.qint8, + mode=quantization_mode, + round_mode="HALF_TO_EVEN") + qint8_tensor = quantize_op[0].eval() + # Updated min-max values should be passed to the next feeding node. + min_value = quantize_op[1].eval() + max_value = quantize_op[2].eval() + shape = tensor_util.TensorShapeProtoToList(input_node.attr["value"] + .tensor.tensor_shape) + qint8_const_node = create_constant_node( + qint8_const_name, qint8_tensor, + dtypes.qint8, + shape=shape) + min_node = create_constant_node(min_name, min_value, dtypes.float32) + max_node = create_constant_node(max_name, max_value, dtypes.float32) + dequantize_node = create_node("Dequantize", input_node.name, + [qint8_const_name, min_name, max_name]) + set_attr_dtype(dequantize_node, "T", dtypes.qint8) + set_attr_string(dequantize_node, "mode", b'SCALED') + return [qint8_const_node, min_node, max_node, dequantize_node] EightbitizeRecursionState = collections.namedtuple( "EightbitizeRecursionState", @@ -322,7 +384,8 @@ class GraphRewriter(object): input_graph, mode, quantized_input_range, - fallback_quantization_range=None): + fallback_quantization_range=None, + intel_cpu_eightbitize=False): """Sets up the class to rewrite a float graph. Args: @@ -344,6 +407,7 @@ class GraphRewriter(object): self.nodes_map = self.create_nodes_map(input_graph) self.output_graph = None self.mode = mode + self.intel_cpu_eightbitize = intel_cpu_eightbitize self.final_node_renames = {} if quantized_input_range: self.input_range = (quantized_input_range[0], quantized_input_range[1]) @@ -417,8 +481,22 @@ class GraphRewriter(object): self.state = EightbitizeRecursionState( already_visited={}, output_node_stack=[], merged_with_fake_quant={}) - for output_node in output_nodes: - self.eightbitize_nodes_recursively(output_node) + + if self.intel_cpu_eightbitize: + # TODO(intel-tf): Enables fused quantized node for intel cpu. + for output_node in output_nodes: + # Intiailize output_node_stack with output node. + # Each element in the stack is a mutable list containing + # [parent_node, index_to_parent, quantization_flag, fusion_flag]. + # In case of root node, make self as parent. + self.state.output_node_stack.append( + [output_node, None, False, False]) + self.intel_cpu_eightbitize_nodes_recursively(output_node) + self.state.output_node_stack.pop() + else: + for output_node in output_nodes: + self.eightbitize_nodes_recursively(output_node) + self.state = None if self.input_range: self.add_output_graph_node( @@ -653,6 +731,200 @@ class GraphRewriter(object): (self.state.output_node_stack[-1][0], current_node.name, current_node.op)) + # TODO(intel-tf): Quantized Conv2D could be fused with few other succeeding + # ops. Current support is for BiasAdd and Relu. Future implementation will + # include: + # (i) Conv2D + {BiasAdd} + Relu + Add + Relu + # (ii) Conv2D + {BiasAdd} + Relu + Add + # (ii) Conv2D + {BiasAdd} + Add + Relu + # (iii) Conv2D + {BiasAdd} + Add + def intel_cpu_eightbitize_conv_node(self, original_node, bias_node=None, + bias_add_name=None, add_node_name=None, + relu_node_name=None): + """Replaces a Conv2D node with the eight bit equivalent sub-graph.""" + all_input_names = self.add_eightbit_prologue_nodes(original_node) + + if bias_node and add_node_name and relu_node_name: + new_node = node_def_pb2.NodeDef() + new_node.CopyFrom(bias_node) + self.add_output_graph_node(new_node) + all_input_names = all_input_names[:2] + [bias_node.name] + \ + all_input_names[2:] + [add_node_name] + quantized_conv_name = original_node.name + "_eightbit_quantized_conv" + quantized_conv_node = create_node("QuantizedConv2DWithBiasSumAndRelu", + quantized_conv_name, all_input_names) + elif bias_node and (not add_node_name) and relu_node_name: + new_node = node_def_pb2.NodeDef() + new_node.CopyFrom(bias_node) + self.add_output_graph_node(new_node) + all_input_names = all_input_names[:2] + [bias_node.name] + \ + all_input_names[2:] + quantized_conv_name = original_node.name + "_eightbit_quantized_conv" + quantized_conv_node = create_node("QuantizedConv2DWithBiasAndRelu", + quantized_conv_name, all_input_names) + elif bias_node and bias_add_name and \ + (not add_node_name) and (not relu_node_name): + new_node = node_def_pb2.NodeDef() + new_node.CopyFrom(bias_node) + self.add_output_graph_node(new_node) + all_input_names = all_input_names[:2] + [bias_node.name] + \ + all_input_names[2:] + quantized_conv_name = original_node.name + "_eightbit_quantized_conv" + quantized_conv_node = create_node("QuantizedConv2DWithBias", + quantized_conv_name, all_input_names) + else: + quantized_conv_name = original_node.name + "_eightbit_quantized_conv" + quantized_conv_node = create_node("QuantizedConv2D", quantized_conv_name, + all_input_names) + copy_attr(quantized_conv_node, "strides", original_node.attr["strides"]) + copy_attr(quantized_conv_node, "padding", original_node.attr["padding"]) + copy_attr(quantized_conv_node, "dilations", original_node.attr["dilations"]) + set_attr_dtype(quantized_conv_node, "Tinput", dtypes.quint8) + set_attr_dtype(quantized_conv_node, "Tfilter", dtypes.qint8) + set_attr_dtype(quantized_conv_node, "out_type", dtypes.qint32) + self.add_output_graph_node(quantized_conv_node) + quantize_down_name = self.add_quantize_down_nodes(original_node, + quantized_conv_name) + if bias_node and relu_node_name: + self.add_dequantize_result_node(quantize_down_name, relu_node_name) + elif bias_node and bias_add_name and \ + (not add_node_name) and (not relu_node_name): + self.add_dequantize_result_node(quantize_down_name, bias_add_name) + else: + self.add_dequantize_result_node(quantize_down_name, original_node.name) + + # TODO(intel-tf): To check whether Conv2D is fed by relu directly or via + # pooling ops. This is required as intel cpu requires input tensor for Conv2D + # to be non-negative. + def intel_cpu_find_relu_recursively(self, current_node): + """Helper function to check if Conv2D is fed by Relu.""" + if current_node.op == "Relu": + return True + else: + first_input_node_name = node_name_from_input(current_node.input[0]) + input_node = self.nodes_map[first_input_node_name] + if input_node.op in ("ConcatV2", "MaxPool", "AvgPool", "Relu"): + return self.intel_cpu_find_relu_recursively(input_node) + else: + return False + + # TODO(intel-tf): We leave the output graph partially quantized for + # intel cpu. Current quantization support is for Conv2D and its fusion. + # More quantized operations will be included as more implementations are + # completed. + def intel_cpu_eightbitize_nodes_recursively(self, current_node): + """The entry point for transforming a graph into full eight bit.""" + if current_node.name in self.state.already_visited: + if (self.should_merge_with_fake_quant_node() or + current_node.name in self.state.merged_with_fake_quant): + raise ValueError("Unsupported graph structure: output of node %s " + "is processed by a FakeQuant* node and should have " + "no other outputs.", current_node.name) + return + + self.state.already_visited[current_node.name] = True + quantize_input, should_quantize_conv, \ + fuse_with_conv = (False, False, False) + + if current_node.op == "Conv2D": + should_quantize_conv = self.intel_cpu_find_relu_recursively(current_node) + + inputs = list(enumerate(current_node.input)) + if current_node.op == "AddN": + inputs = reversed(inputs) # pylint: disable=redefined-variable-type + + for i, input_node_name in inputs: + input_node_name = node_name_from_input(input_node_name) + input_node = self.nodes_map[input_node_name] + + if should_quantize_conv and i == 1 and input_node.op == "Const": + quantize_input = True + + self.state.output_node_stack.append([current_node, i, quantize_input, + fuse_with_conv]) + self.intel_cpu_eightbitize_nodes_recursively(input_node) + self.state.output_node_stack.pop() + + if current_node.op == "Conv2D" and should_quantize_conv and quantize_input: + # match pattern for fusion with bias and relu + grand_parent, parent = self.state.output_node_stack[-2:] + if parent[0].op == "BiasAdd" and grand_parent[0].op == "Relu": + self.state.output_node_stack[-2][3] = True # BiasAdd to be fused + self.state.output_node_stack[-3][3] = True # Relu to be fused + bias_node_name = node_name_from_input(parent[0].input[1]) + bias_node = self.nodes_map[bias_node_name] + self.intel_cpu_eightbitize_conv_node(current_node, bias_node, None, + None, grand_parent[0].name) + elif parent[0].op == "BiasAdd" and grand_parent[0].op == "AddN": + grand_grand_parent = self.state.output_node_stack[-3] + if grand_grand_parent[0].op == "Relu" \ + and (not self.state.output_node_stack[-3][3]) \ + and (not self.state.output_node_stack[-4][3]): + self.state.output_node_stack[-2][3] = True # BiasAdd to be fused + self.state.output_node_stack[-3][3] = True # AddN to be fused + self.state.output_node_stack[-4][3] = True # Relu to be fused + bias_node_name = node_name_from_input(parent[0].input[1]) + bias_node = self.nodes_map[bias_node_name] + add_node_name = node_name_from_input(grand_parent[0].input[0]) + self.intel_cpu_eightbitize_conv_node(current_node, bias_node, None, + add_node_name, + grand_grand_parent[0].name) + elif not self.state.output_node_stack[-2][3]: # Fuse BiasAdd then + self.state.output_node_stack[-2][3] = True # BiasAdd to be fused + bias_node_name = node_name_from_input(parent[0].input[1]) + bias_node = self.nodes_map[bias_node_name] + self.intel_cpu_eightbitize_conv_node(current_node, bias_node, + parent[0].name) + else: + self.intel_cpu_eightbitize_conv_node(current_node) + elif parent[0].op == "BiasAdd" and \ + (not self.state.output_node_stack[-2][3]): + self.state.output_node_stack[-2][3] = True # BiasAdd to be fused + bias_node_name = node_name_from_input(parent[0].input[1]) + bias_node = self.nodes_map[bias_node_name] + self.intel_cpu_eightbitize_conv_node(current_node, bias_node, + parent[0].name) + else: + self.intel_cpu_eightbitize_conv_node(current_node) + elif current_node.op == "BiasAdd" and \ + self.state.output_node_stack[-1][3]: + pass # This op is already processed by fused quantization + elif current_node.op == "Relu" and \ + self.state.output_node_stack[-1][3]: + pass # This op is already processed by fused quantization + elif current_node.op == "AddN" and \ + self.state.output_node_stack[-1][3]: + pass # AddN op is already processed by fused quatization + elif current_node.op == "MaxPool" or current_node.op == "AvgPool": + self.eightbitize_single_input_tensor_node(current_node, + self.add_pool_function) + elif (current_node.op == "ConcatV2" and + dtypes.as_dtype(current_node.attr["T"].type) == dtypes.float32): + self.eightbitize_concatv2_node(current_node) + elif current_node.op == "Const": + parent = self.state.output_node_stack[-1] + if parent[0].op == "Conv2D" and parent[2]: + for n in intel_cpu_quantize_weight_eightbit(current_node, b"SCALED"): + self.add_output_graph_node(n) + elif parent[0].op == "BiasAdd" and \ + self.state.output_node_stack[-2][3]: + pass # This constant is already process by fused quantization + else: + new_node = node_def_pb2.NodeDef() + new_node.CopyFrom(current_node) + self.add_output_graph_node(new_node) + else: + new_node = node_def_pb2.NodeDef() + new_node.CopyFrom(current_node) + self.add_output_graph_node(new_node) + + if (self.should_merge_with_fake_quant_node() and + current_node.name not in self.state.merged_with_fake_quant): + raise ValueError( + "FakeQuant* node %s failed to merge with node %s of type %s" % + (self.state.output_node_stack[-1][0], current_node.name, + current_node.op)) + def add_eightbit_prologue_nodes(self, original_node): """Adds input conversion nodes to handle quantizing the underlying node.""" namespace_prefix = original_node.name + "_eightbit" @@ -712,7 +984,11 @@ class GraphRewriter(object): "QuantizeV2", quantize_input_name, [original_input_name, min_input_name, max_input_name]) set_attr_dtype(quantize_input_node, "T", dtypes.quint8) - set_attr_string(quantize_input_node, "mode", b"MIN_FIRST") + set_attr_string(quantize_input_node, "mode", + b"SCALED" if self.intel_cpu_eightbitize else b"MIN_FIRST") + set_attr_string(quantize_input_node, "round_mode", + b"HALF_TO_EVEN" if self.intel_cpu_eightbitize + else b"HALF_AWAY_FROM_ZERO") self.add_output_graph_node(quantize_input_node) min_output_name = quantize_input_name + ":1" max_output_name = quantize_input_name + ":2" @@ -965,6 +1241,44 @@ class GraphRewriter(object): self.add_output_graph_node(quantized_concat_node) self.add_dequantize_result_node(quantized_concat_name, original_node.name) + def eightbitize_concatv2_node(self, original_node): + """ + Args: + original_node: Float node to be converted. + + Returns: + Subgraph representing the quantized version of the original node. + + """ + namespace_prefix = original_node.name + "_eightbit" + quantized_concat_name = namespace_prefix + "_quantized_concatv2" + reshape_dims_name, reduction_dims_name = self.add_common_quantization_nodes( + namespace_prefix) + num_input = len(original_node.input) + shape_input_name = original_node.input[num_input-1] + original_inputs = original_node.input[0:num_input-1] + input_names = [] + min_names = [] + max_names = [] + for original_input_name in original_inputs: + quantize_input_name, min_input_name, max_input_name = ( + self.eightbitize_input_to_node(namespace_prefix, original_input_name, + reshape_dims_name, + reduction_dims_name)) + input_names.append(quantize_input_name) + min_names.append(min_input_name) + max_names.append(max_input_name) + all_input_names = input_names + all_input_names.append(shape_input_name) + all_input_names.extend(min_names) + all_input_names.extend(max_names) + quantized_concat_node = create_node("QuantizedConcatV2", + quantized_concat_name, all_input_names) + set_attr_int(quantized_concat_node, "N", len(original_inputs)) + set_attr_dtype(quantized_concat_node, "T", dtypes.quint8) + self.add_output_graph_node(quantized_concat_node) + self.add_dequantize_result_node(quantized_concat_name, original_node.name) + def eightbitize_placeholder_node(self, current_node): """Replaces a placeholder node with a quint8 placeholder node+dequantize.""" name = current_node.name @@ -1249,7 +1563,6 @@ class GraphRewriter(object): self.input_graph = new_input_graph self.nodes_map = self.create_nodes_map(self.input_graph) - def main(unused_args): if not gfile.Exists(FLAGS.input): print("Input graph file '" + FLAGS.input + "' does not exist!") @@ -1264,9 +1577,14 @@ def main(unused_args): return -1 tf_graph = graph_pb2.GraphDef() - with gfile.Open(FLAGS.input, "rb") as f: + # TODO(intel-tf): Enabling user to work with both binary and text format. + mode = "rb" if FLAGS.input_binary else "r" + with gfile.Open(FLAGS.input, mode) as f: data = f.read() - tf_graph.ParseFromString(data) + if FLAGS.input_binary: + tf_graph.ParseFromString(data) + else: + text_format.Merge(data, tf_graph) graph = ops.Graph() with graph.as_default(): @@ -1287,16 +1605,21 @@ def main(unused_args): FLAGS.quantized_fallback_min, FLAGS.quantized_fallback_max ] - rewriter = GraphRewriter(tf_graph, FLAGS.mode, quantized_input_range, - fallback_quantization_range) + rewriter = GraphRewriter(tf_graph, FLAGS.mode, + quantized_input_range, fallback_quantization_range, + FLAGS.intel_cpu_eightbitize) output_graph = rewriter.rewrite(FLAGS.output_node_names.split(",")) - f = gfile.FastGFile(FLAGS.output, "wb") - f.write(output_graph.SerializeToString()) + # TODO(intel-tf): Enabling user to work with both binary and text format. + mode = "wb" if FLAGS.output_binary else "w" + f = gfile.FastGFile(FLAGS.output, mode) + if FLAGS.output_binary: + f.write(output_graph.SerializeToString()) + else: + f.write(str(output_graph)) return 0 - if __name__ == "__main__": app.run() -- GitLab From b6d0889f38bcfe266df6c8ad8f330c1b390f5b9b Mon Sep 17 00:00:00 2001 From: Shimin Guo Date: Tue, 14 Aug 2018 23:12:48 -0700 Subject: [PATCH 0007/1825] export symbols in the stream_executor namespace Some of these symbols were previously in the perftools::gputools namespace. Not having these symbols causes custom ops loading to fail in the monolithic mode. For example, bazel test --config monolithic //tensorflow/contrib/rnn:ops/gru_ops_test --- tensorflow/tf_exported_symbols.lds | 1 + tensorflow/tf_version_script.lds | 1 + 2 files changed, 2 insertions(+) diff --git a/tensorflow/tf_exported_symbols.lds b/tensorflow/tf_exported_symbols.lds index 3ff824e5e1..9f6114f503 100644 --- a/tensorflow/tf_exported_symbols.lds +++ b/tensorflow/tf_exported_symbols.lds @@ -5,3 +5,4 @@ *TFE_* *nsync_* *pywrap_xla* +*stream_executor* diff --git a/tensorflow/tf_version_script.lds b/tensorflow/tf_version_script.lds index 6b28943f01..39d258c3b7 100644 --- a/tensorflow/tf_version_script.lds +++ b/tensorflow/tf_version_script.lds @@ -6,6 +6,7 @@ tensorflow { *TFE_*; *nsync_*; *pywrap_xla*; + *stream_executor*; local: *; }; -- GitLab From 9039a5e1ef2fcab237c3c736826068cf1c9f8094 Mon Sep 17 00:00:00 2001 From: Mahmoud Abuzaina Date: Fri, 17 Aug 2018 18:58:05 -0700 Subject: [PATCH 0008/1825] Fixed merge conflicts --- tensorflow/core/api_def/BUILD | 5 + tensorflow/core/api_def/excluded_ops.cc | 14 +- tensorflow/core/ops/nn_ops.cc | 339 +++++++++++++++++++++++- 3 files changed, 356 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/api_def/BUILD b/tensorflow/core/api_def/BUILD index 06b797e32e..f610facd75 100644 --- a/tensorflow/core/api_def/BUILD +++ b/tensorflow/core/api_def/BUILD @@ -17,6 +17,10 @@ load( "tf_cc_binary", "tf_cc_test", ) +load( + "//third_party/mkl:build_defs.bzl", + "if_mkl", +) filegroup( name = "base_api_def", @@ -40,6 +44,7 @@ cc_library( name = "excluded_ops_lib", srcs = ["excluded_ops.cc"], hdrs = ["excluded_ops.h"], + copts = if_mkl(["-DINTEL_MKL=1"]), ) cc_library( diff --git a/tensorflow/core/api_def/excluded_ops.cc b/tensorflow/core/api_def/excluded_ops.cc index 931c943dbc..3db69f6af8 100644 --- a/tensorflow/core/api_def/excluded_ops.cc +++ b/tensorflow/core/api_def/excluded_ops.cc @@ -21,7 +21,19 @@ const std::unordered_set* GetExcludedOps() { static std::unordered_set* excluded_ops = new std::unordered_set( {"BigQueryReader", "GenerateBigQueryReaderPartitions", - "GcsConfigureBlockCache", "GcsConfigureCredentials"}); + "GcsConfigureBlockCache", "GcsConfigureCredentials", +#ifdef INTEL_MKL + // QuatizedFusedOps for Intel CPU + "QuantizedConv2DAndRequantize", "QuantizedConv2DWithBias", + "QuantizedConv2DWithBiasAndRequantize", "QuantizedConv2DAndRelu", + "QuantizedConv2DAndReluAndRequantize", + "QuantizedConv2DWithBiasAndRelu", + "QuantizedConv2DWithBiasAndReluAndRequantize", + "QuantizedConv2DWithBiasSumAndRelu", + "QuantizedConv2DWithBiasSumAndReluAndRequantize", + "QuantizedConv2DWithBiasSignedSumAndReluAndRequantize" +#endif // INTEL_MKL + }); return excluded_ops; } } // namespace tensorflow diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 385021b168..199176e93f 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -2241,7 +2241,7 @@ REGISTER_OP("_MklToTf") .Input("input: T") .Input("mkl_input: uint8") .Output("output: T") - .Attr("T: {half, float, double}") + .Attr("T: {half, float, double, qint8, quint8, qint32}") .Attr(GetConvnetDataFormat2D3DAttrString()) .SetShapeFn(shape_inference::UnknownShape) .Doc(R"doc( @@ -2274,6 +2274,343 @@ element-wise MKL op. NOTE Do not invoke this operator directly in Python. Graph rewrite pass is expected to invoke these operators. )doc"); + +REGISTER_OP("QuantizedConv2DAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("out_type: quantizedtype = DT_QINT8") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +// Fusion of Quantized Conv2D and BiasAdd. +REGISTER_OP("QuantizedConv2DWithBias") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: float") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("QuantizedConv2DWithBiasAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: Tbias") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("Tbias: {float, qint32}") + .Attr("out_type: quantizedtype = DT_QINT8") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(8), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +// Fusion of Quantized Conv2D and Relu. +REGISTER_OP("QuantizedConv2DAndRelu") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("QuantizedConv2DAndReluAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("out_type: quantizedtype = DT_QUINT8") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +// Fusion of Quantized Conv2D, BiasAdd and Relu. +REGISTER_OP("QuantizedConv2DWithBiasAndRelu") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: float") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +// Fusion of Quantized Conv2D, BiasAdd, Relu, and Requantize. +REGISTER_OP("QuantizedConv2DWithBiasAndReluAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: Tbias") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("Tbias: {float, qint32}") + .Attr("out_type: quantizedtype = DT_QUINT8") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(8), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +// Fusion of Quantized Conv2D, BiasAdd, Sum, and Relu. +REGISTER_OP("QuantizedConv2DWithBiasSumAndRelu") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: float") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("summand: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("QuantizedConv2DWithBiasSumAndReluAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: Tbias") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Input("summand: Tsummand") + .Input("min_summand: float") + .Input("max_summand: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("Tbias: {float, qint32}") + .Attr("Tsummand: quantizedtype") + .Attr("out_type: quantizedtype = DT_QUINT8") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(8), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("QuantizedConv2DWithBiasSignedSumAndReluAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: Tbias") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Input("summand: Tsummand") + .Input("min_summand: float") + .Input("max_summand: float") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("Tbias: {float, qint32}") + .Attr("Tsummand: quantizedtype") + .Attr("out_type: quantizedtype = DT_QUINT8") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(8), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + #endif // INTEL_MKL } // namespace tensorflow -- GitLab From 2b25a421cbdbde78e82877c76b128446a7ba8db8 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 25 Aug 2018 19:27:33 +0000 Subject: [PATCH 0009/1825] Add `ENV LANG C.UTF-8` to Dockerfiles for Python This fix tries to address the issue raised in 20380 where LANG env was not set in Docker images, and was causing issues with python 3 (related https://bugs.python.org/issue19846). This fix adds `ENV LANG C.UTF-8` to Dockerfiles which matches the official python images. This fix fixes 20380. Signed-off-by: Yong Tang --- .../tools/dockerfiles/partials/python.partial.Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/tools/dockerfiles/partials/python.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/python.partial.Dockerfile index 6f346236a5..ee08af73a8 100644 --- a/tensorflow/tools/dockerfiles/partials/python.partial.Dockerfile +++ b/tensorflow/tools/dockerfiles/partials/python.partial.Dockerfile @@ -3,6 +3,9 @@ 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 -- GitLab From 62811483f9e219622bc08eed056b632b566b9059 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 25 Aug 2018 19:31:08 +0000 Subject: [PATCH 0010/1825] Rebuild the Dockerfiles with: ``` python3 ./assembler.py -o dockerfiles -s spec.yml ``` Signed-off-by: Yong Tang --- .../tools/dockerfiles/dockerfiles/cpu-devel-jupyter.Dockerfile | 3 +++ tensorflow/tools/dockerfiles/dockerfiles/cpu-devel.Dockerfile | 3 +++ .../tools/dockerfiles/dockerfiles/cpu-jupyter.Dockerfile | 3 +++ tensorflow/tools/dockerfiles/dockerfiles/cpu.Dockerfile | 3 +++ .../dockerfiles/dockerfiles/nvidia-devel-jupyter.Dockerfile | 3 +++ .../tools/dockerfiles/dockerfiles/nvidia-devel.Dockerfile | 3 +++ .../tools/dockerfiles/dockerfiles/nvidia-jupyter.Dockerfile | 3 +++ tensorflow/tools/dockerfiles/dockerfiles/nvidia.Dockerfile | 3 +++ 8 files changed, 24 insertions(+) diff --git a/tensorflow/tools/dockerfiles/dockerfiles/cpu-devel-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/cpu-devel-jupyter.Dockerfile index dbbad7d03a..dab7178db3 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/cpu-devel-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/cpu-devel-jupyter.Dockerfile @@ -65,6 +65,9 @@ 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 diff --git a/tensorflow/tools/dockerfiles/dockerfiles/cpu-devel.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/cpu-devel.Dockerfile index 160d7c02e2..68566ccc8a 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/cpu-devel.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/cpu-devel.Dockerfile @@ -63,6 +63,9 @@ 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 diff --git a/tensorflow/tools/dockerfiles/dockerfiles/cpu-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/cpu-jupyter.Dockerfile index 8d5d653ab7..f889ed6f91 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/cpu-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/cpu-jupyter.Dockerfile @@ -45,6 +45,9 @@ 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 diff --git a/tensorflow/tools/dockerfiles/dockerfiles/cpu.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/cpu.Dockerfile index 35c41b49fd..182a534bed 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/cpu.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/cpu.Dockerfile @@ -43,6 +43,9 @@ 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 diff --git a/tensorflow/tools/dockerfiles/dockerfiles/nvidia-devel-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/nvidia-devel-jupyter.Dockerfile index 0f5fedf2fe..1552179302 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/nvidia-devel-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/nvidia-devel-jupyter.Dockerfile @@ -85,6 +85,9 @@ 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 diff --git a/tensorflow/tools/dockerfiles/dockerfiles/nvidia-devel.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/nvidia-devel.Dockerfile index a6e280082e..6027976130 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/nvidia-devel.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/nvidia-devel.Dockerfile @@ -83,6 +83,9 @@ 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 diff --git a/tensorflow/tools/dockerfiles/dockerfiles/nvidia-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/nvidia-jupyter.Dockerfile index f1799113b1..464259e6db 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/nvidia-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/nvidia-jupyter.Dockerfile @@ -66,6 +66,9 @@ 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 diff --git a/tensorflow/tools/dockerfiles/dockerfiles/nvidia.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/nvidia.Dockerfile index 690eb68b22..25e0dd3a4d 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/nvidia.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/nvidia.Dockerfile @@ -64,6 +64,9 @@ 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 -- GitLab From abda03facee57e5d94a6e2d7be26ab53e319dede Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Thu, 16 Aug 2018 16:58:30 -0700 Subject: [PATCH 0011/1825] Fast bilinear resize code Cleanup code formatting in Bilinear Resize Updates requested by reviewers --- tensorflow/core/kernels/BUILD | 13 +- tensorflow/core/kernels/crop_and_resize_op.cc | 94 +- .../crop_and_resize_op_benchmark_test.cc | 36 +- .../core/kernels/crop_resize_bilinear_core.h | 5497 +++++++++++++++++ tensorflow/core/kernels/resize_bilinear_op.cc | 151 +- .../core/kernels/resize_bilinear_op_test.cc | 2 +- 6 files changed, 5592 insertions(+), 201 deletions(-) create mode 100644 tensorflow/core/kernels/crop_resize_bilinear_core.h diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 972fb9efa9..3b2b71ec2a 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -29,6 +29,7 @@ package_group( load( "//tensorflow:tensorflow.bzl", "if_android", + "if_linux_x86_64", "tf_cc_test", "tf_cc_tests", "tf_cc_binary", @@ -551,6 +552,12 @@ cc_header_only_library( deps = [":image_resizer_state"], ) +cc_library( + name = "crop_resize_bilinear_core", + hdrs = ["crop_resize_bilinear_core.h"], + visibility = ["//visibility:private"], +) + # OpKernel libraries ---------------------------------------------------------- ARRAY_DEPS = [ @@ -2150,7 +2157,8 @@ tf_kernel_library( tf_kernel_library( name = "crop_and_resize_op", prefix = "crop_and_resize_op", - deps = IMAGE_DEPS, + copts = tf_copts() + if_linux_x86_64(["-msse4.1 -finline-functions"]), + deps = IMAGE_DEPS + [":crop_resize_bilinear_core"], ) tf_kernel_library( @@ -2216,7 +2224,8 @@ tf_kernel_library( tf_kernel_library( name = "resize_bilinear_op", prefix = "resize_bilinear_op", - deps = IMAGE_DEPS, + copts = tf_copts() + if_linux_x86_64(["-msse4.1 -finline-functions"]), + deps = IMAGE_DEPS + [":crop_resize_bilinear_core"], ) tf_kernel_library( diff --git a/tensorflow/core/kernels/crop_and_resize_op.cc b/tensorflow/core/kernels/crop_and_resize_op.cc index 99d01b4db6..7c4d3431e6 100644 --- a/tensorflow/core/kernels/crop_and_resize_op.cc +++ b/tensorflow/core/kernels/crop_and_resize_op.cc @@ -22,17 +22,18 @@ limitations under the License. #include #include -#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/framework/types.h" #include "tensorflow/core/kernels/bounds_check.h" +#include "tensorflow/core/kernels/crop_resize_bilinear_core.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/types.h" #include "tensorflow/core/util/work_sharder.h" +#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #if GOOGLE_CUDA #include "tensorflow/core/common_runtime/gpu/gpu_event_mgr.h" @@ -228,61 +229,56 @@ struct CropAndResize { continue; } - const float height_scale = - (crop_height > 1) - ? (y2 - y1) * (image_height - 1) / (crop_height - 1) - : 0; - const float width_scale = - (crop_width > 1) ? (x2 - x1) * (image_width - 1) / (crop_width - 1) - : 0; - - for (int y = 0; y < crop_height; ++y) { - const float in_y = (crop_height > 1) - ? y1 * (image_height - 1) + y * height_scale - : 0.5 * (y1 + y2) * (image_height - 1); - if (in_y < 0 || in_y > image_height - 1) { - for (int x = 0; x < crop_width; ++x) { - for (int d = 0; d < depth; ++d) { - crops(b, y, x, d) = extrapolation_value; - } - } - continue; + if (method_name == "bilinear") { + std::vector xs; + std::vector ys; + int min_ix, max_ix, min_iy, max_iy; + compute_interpolation_weights(crop_width, image_width, x1, x2, + &min_ix, &max_ix, &xs); + compute_interpolation_weights(crop_height, image_height, y1, y2, + &min_iy, &max_iy, &ys); + + // multiply by depth to avoid multiplication in resize_single_image. + for (int i = min_ix; i <= max_ix; ++i) { + xs[i - min_ix].lower *= depth; + xs[i - min_ix].upper *= depth; } - if (method_name == "bilinear") { - const int top_y_index = floorf(in_y); - const int bottom_y_index = ceilf(in_y); - const float y_lerp = in_y - top_y_index; - for (int x = 0; x < crop_width; ++x) { - const float in_x = (crop_width > 1) - ? x1 * (image_width - 1) + x * width_scale - : 0.5 * (x1 + x2) * (image_width - 1); - if (in_x < 0 || in_x > image_width - 1) { + crop_resize_single_image_common( + image.data() + + static_cast(b_in) * static_cast(image_height) * + static_cast(image_width) * + static_cast(depth), + image_height, image_width, crop_height, crop_width, depth, min_ix, + max_ix, xs.data(), min_iy, max_iy, ys.data(), extrapolation_value, + false, false, + crops.data() + + static_cast(b) * static_cast(crop_height) * + static_cast(crop_width) * + static_cast(depth)); + // xs and ys are deallocated automatically when they go out of scope + } else { // method == "nearest" + const float height_scale = + (crop_height > 1) + ? (y2 - y1) * (image_height - 1) / (crop_height - 1) + : 0; + const float width_scale = + (crop_width > 1) + ? (x2 - x1) * (image_width - 1) / (crop_width - 1) + : 0; + + for (int y = 0; y < crop_height; ++y) { + const float in_y = (crop_height > 1) + ? y1 * (image_height - 1) + y * height_scale + : 0.5 * (y1 + y2) * (image_height - 1); + if (in_y < 0 || in_y > image_height - 1) { + for (int x = 0; x < crop_width; ++x) { for (int d = 0; d < depth; ++d) { crops(b, y, x, d) = extrapolation_value; } - continue; - } - const int left_x_index = floorf(in_x); - const int right_x_index = ceilf(in_x); - const float x_lerp = in_x - left_x_index; - - for (int d = 0; d < depth; ++d) { - const float top_left(static_cast( - image(b_in, top_y_index, left_x_index, d))); - const float top_right(static_cast( - image(b_in, top_y_index, right_x_index, d))); - const float bottom_left(static_cast( - image(b_in, bottom_y_index, left_x_index, d))); - const float bottom_right(static_cast( - image(b_in, bottom_y_index, right_x_index, d))); - const float top = top_left + (top_right - top_left) * x_lerp; - const float bottom = - bottom_left + (bottom_right - bottom_left) * x_lerp; - crops(b, y, x, d) = top + (bottom - top) * y_lerp; } + continue; } - } else { // method == "nearest" for (int x = 0; x < crop_width; ++x) { const float in_x = (crop_width > 1) ? x1 * (image_width - 1) + x * width_scale diff --git a/tensorflow/core/kernels/crop_and_resize_op_benchmark_test.cc b/tensorflow/core/kernels/crop_and_resize_op_benchmark_test.cc index d7ca64bea0..54d4f33b44 100644 --- a/tensorflow/core/kernels/crop_and_resize_op_benchmark_test.cc +++ b/tensorflow/core/kernels/crop_and_resize_op_benchmark_test.cc @@ -21,11 +21,13 @@ limitations under the License. namespace tensorflow { +template static Graph* BM_CropAndResize(int batches, int width, int height, int depth, int crop_height, int crop_width) { Graph* g = new Graph(OpRegistry::Global()); - Tensor in(DT_FLOAT, TensorShape({batches, height, width, depth})); - in.flat().setRandom(); + Tensor in(DataTypeToEnum::v(), + TensorShape({batches, height, width, depth})); + in.flat().setRandom(); Tensor boxes(DT_FLOAT, TensorShape({batches, 4})); auto boxes_tensor = boxes.matrix(); Tensor box_ind(DT_INT32, TensorShape({batches})); @@ -51,13 +53,17 @@ static Graph* BM_CropAndResize(int batches, int width, int height, int depth, return g; } -#define BM_CropAndResizeDev(DEVICE, B, W, H, D, CH, CW) \ - static void BM_CropAndResize_##DEVICE##_##B##_##W##_##H##_##D##_##CH##_##CW( \ - int iters) { \ - testing::ItemsProcessed(iters* B* W* H* D); \ - test::Benchmark(#DEVICE, BM_CropAndResize(B, W, H, D, CH, CW)).Run(iters); \ - } \ - BENCHMARK(BM_CropAndResize_##DEVICE##_##B##_##W##_##H##_##D##_##CH##_##CW); +#define BM_CropAndResizeDev(DEVICE, DTYPE, B, W, H, D, CH, CW) \ + static void \ + BM_CropAndResize_##DEVICE##_##DTYPE##_##B##_##W##_##H##_##D##_##CH##_##CW( \ + int iters) { \ + testing::ItemsProcessed(iters* B* W* H* D); \ + test::Benchmark(#DEVICE, BM_CropAndResize::Type>( \ + B, W, H, D, CH, CW)) \ + .Run(iters); \ + } \ + BENCHMARK( \ + BM_CropAndResize_##DEVICE##_##DTYPE##_##B##_##W##_##H##_##D##_##CH##_##CW); // Benchmark results using CPU:Intel Haswell with HyperThreading (6 cores) // Benchmark Time(ns) CPU(ns) Iterations @@ -65,8 +71,14 @@ static Graph* BM_CropAndResize(int batches, int width, int height, int depth, // BM_CropAndResize_cpu_1_640_640_1_512_512 3801232 3914692 185 99.784M items/s // BM_CropAndResize_cpu_1_80_80_512_7_7 182470 241767 2941 1.372G items/s -BM_CropAndResizeDev(cpu, 1, 640, 640, 3, 512, 512); -BM_CropAndResizeDev(cpu, 1, 640, 640, 1, 512, 512); -BM_CropAndResizeDev(cpu, 1, 80, 80, 512, 7, 7); +BM_CropAndResizeDev(cpu, DT_UINT8, 1, 640, 640, 3, 512, 512); +BM_CropAndResizeDev(cpu, DT_UINT8, 1, 640, 640, 1, 512, 512); + +BM_CropAndResizeDev(cpu, DT_HALF, 1, 640, 640, 3, 512, 512); +BM_CropAndResizeDev(cpu, DT_HALF, 1, 640, 640, 1, 512, 512); + +BM_CropAndResizeDev(cpu, DT_FLOAT, 1, 640, 640, 3, 512, 512); +BM_CropAndResizeDev(cpu, DT_FLOAT, 1, 640, 640, 1, 512, 512); +BM_CropAndResizeDev(cpu, DT_FLOAT, 1, 80, 80, 512, 7, 7); } // namespace tensorflow diff --git a/tensorflow/core/kernels/crop_resize_bilinear_core.h b/tensorflow/core/kernels/crop_resize_bilinear_core.h new file mode 100644 index 0000000000..f6846d6a55 --- /dev/null +++ b/tensorflow/core/kernels/crop_resize_bilinear_core.h @@ -0,0 +1,5497 @@ +/* Copyright 2015 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_CORE_KERNELS_CROP_RESIZE_BILINEAR_CORE_H_ +#define TENSORFLOW_CORE_KERNELS_CROP_RESIZE_BILINEAR_CORE_H_ + +// only include intrinsics when the appropriate flags call for it, +// since these headers only exists on x86 platforms. +#ifdef __SSE4_1__ +#include +#include +#include +#endif +#ifdef __AVX2__ +#include +#endif +#include +#include +#include +#include +#include + +namespace tensorflow { +namespace { + +// Compute the interpolation indices only once. +struct CachedInterpolation { + int lower; // Lower source index used in the interpolation + int upper; // Upper source index used in the interpolation + // 1-D linear iterpolation scale (see: + // https://en.wikipedia.org/wiki/Bilinear_interpolation) + float lerp; +}; + +bool compute_single_interpolation_weight(const int in_size, + const float out2in_scale, + const float out2in_start, + const bool clip, const int i, + int* lower, int* upper, float* lerp) { + const float in = i * out2in_scale + out2in_start; + *lower = (int)floor(in); + *upper = (int)ceil(in); + *lerp = (float)(in - (float)*lower); + if (clip) { + if (*lower < 0) + *lower = 0; + else if (*lower >= in_size) + *lower = in_size - 1; + if (*upper < 0) + *upper = 0; + else if (*upper >= in_size) + *upper = in_size - 1; + return true; + } else { + return (*lower >= 0 && *upper < in_size) ? true : false; + } +} +/** + * Compute interpolation values for output indexes in range + * [out_start,out_start+out_size-1]. + * Returns true if all output indexes have lower and upper (input) indexes + * within range [0,in_size-1]. + */ +bool compute_interpolation_weights(const int min_i, const int max_i, + const int in_size, const float out2in_scale, + const float out2in_start, const bool clip, + CachedInterpolation* interpolation) { + bool rval = true; + int num_i = max_i - min_i + 1; + for (int i = 0; i < num_i; ++i) { + if (!compute_single_interpolation_weight( + in_size, out2in_scale, out2in_start, clip, i + min_i, + &interpolation[i].lower, &interpolation[i].upper, + &interpolation[i].lerp)) { + rval = false; + } + } + return rval; +} +/** + * Compatibility method for resize_bilinear_op.cc + */ +void compute_interpolation_weights(const int out_size, const int in_size, + const float out2in_scale, + CachedInterpolation* interpolation) { + interpolation[out_size].lower = 0; + interpolation[out_size].upper = 0; + const bool clip = true; + if (!compute_interpolation_weights(0, out_size - 1, in_size, out2in_scale, + 0.0f, clip, interpolation)) { + // Should never happen, check for it anyway + printf( + "Warning! Interpolation values have lower,upper indexes outside of " + "range [0,in_size-1]\n"); + } +} +/** + * Compute minimum and maximum (output) i where both lower and upper (input) is + * in range [0,in_size-1] + * If no values of i satisfy condition, min_i = in_size, max_i = -1 and method + * returns false. + * Returns true if min_i >= max_i. + */ +bool compute_minmax_indexes(const int out_size, const int in_size, + const float out2in_scale, const float out2in_start, + int* min_i, int* max_i) { + *min_i = out_size; + *max_i = -1; + int lower, upper; + float lerp; + for (int i = 0; i < out_size; ++i) { + if (compute_single_interpolation_weight(in_size, out2in_scale, out2in_start, + false, i, &lower, &upper, &lerp)) { + if (i < *min_i) *min_i = i; + if (i > *max_i) *max_i = i; + } + } + return (*min_i <= *max_i) ? true : false; +} +/** + * Compute interpolation weights for crop_and_resize_op.cc + * Also computes extrapolation areas. + * Returns true if at least one point requires interpolation, false otherwise. + */ +bool compute_interpolation_weights( + const int out_size, const int in_size, + const float x1, // lower bounding box, crop region starts at in_size*x1 + const float x2, // upper bounding box, crop region ends at in_size*x2 + int* min_i, int* max_i, std::vector* interpolation) { + float out2in_start = out_size > 1 + ? (float)(in_size - 1) * (float)x1 + : (float)(in_size - 1) * (float)(x1 + x2) / 2.0f; + float out2in_scale = + out_size > 1 + ? (float)(x2 - x1) * (float)(in_size - 1) / (float)(out_size - 1) + : 0.0f; + if (compute_minmax_indexes(out_size, in_size, out2in_scale, out2in_start, + min_i, max_i)) { + interpolation->resize(*max_i - *min_i + 1); + bool all_inputs_ok = compute_interpolation_weights( + *min_i, *max_i, in_size, out2in_scale, out2in_start, false, + interpolation->data()); + if (!all_inputs_ok) { + // should never happen, purpose of compute_minmax_indexes is to ensure + // that all inputs are ok. + printf( + "Error! compute_interpolation_weights returned input indexes outside " + "valid range - SEGV will likely ensue.\n"); + } + return true; + } else { + return false; + } +} + +/** + * Cast float v to type U with range clamping. + * + * If vmax_val, + * return value is clamped to u_max_val. + */ +template +U cast_to(float v, float min_val, float max_val, U u_min_val, U u_max_val); +template +U cast_to(float v, float min_val, float max_val, U u_min_val, U u_max_val) { + if (v < min_val) + return u_min_val; + else if (v > max_val) + return u_max_val; + else + return static_cast(v); +} +/** + * no-op cast from float to float. + */ +template <> +float cast_to(float v, float min_val, float max_val, float u_min_val, + float u_max_val) { + return v; +} + +float compute_lerp(const float top_left, const float top_right, + const float bottom_left, const float bottom_right, + const float x_lerp, const float y_lerp) { + const float top = top_left + (top_right - top_left) * x_lerp; + const float bottom = bottom_left + (bottom_right - bottom_left) * x_lerp; + return top + (bottom - top) * y_lerp; +} + +/** + * Computes the bilinear interpolation from the appropriate 4 float points + * and the linear interpolation weights. + * Accepts input tensors of type T and produces output tensors of type U. + * Optionally flips horizontal and/or vertical axis. + */ +template +void crop_resize_single_image(const T* image, const int64 in_height, + const int64 in_width, const int64 out_height, + const int64 out_width, const int channels, + const int min_ix, const int max_ix, + const CachedInterpolation* xs, const int min_iy, + const int max_iy, const CachedInterpolation* ys, + const float extrapolated_value, const bool flip_x, + const bool flip_y, + U* output) TF_ATTRIBUTE_NOINLINE; +template +void crop_resize_single_image(const T* image, const int64 in_height, + const int64 in_width, const int64 out_height, + const int64 out_width, const int channels, + const int min_ix, const int max_ix, + const CachedInterpolation* xs, const int min_iy, + const int max_iy, const CachedInterpolation* ys, + const float extrapolated_value, const bool flip_x, + const bool flip_y, U* output) { + const int64 in_row_size = in_width * channels; + const int64 out_row_size = out_width * channels; + U u_min_val = std::numeric_limits::min(); + U u_max_val = std::numeric_limits::max(); + float min_val = static_cast(u_min_val); + float max_val = static_cast(u_max_val); + U uEx = + cast_to(extrapolated_value, min_val, max_val, u_min_val, u_max_val); + // low y extrapolation zone + if (min_iy > 0) { + U* p = flip_y ? output + out_row_size * (out_height - min_iy) : output; + int64 nn = out_row_size * (int64)min_iy; + for (int64 i = 0; i < nn; ++i) p[i] = uEx; + } + // high y extrapolation zone + if (max_iy < out_height - 1) { + U* p = flip_y ? output : output + out_row_size * (max_iy + 1); + int64 nn = out_row_size * (int64)(out_height - 1 - max_iy); + for (int64 i = 0; i < nn; ++i) p[i] = uEx; + } + // low x extrapolation zone + if (min_ix > 0) { + for (int iy = min_iy; iy <= max_iy; ++iy) { + int xx0 = flip_x ? (out_width - min_ix) * channels : 0; + int nxx = min_ix * channels; + U* p = output + xx0 + + out_row_size * (int64)(flip_y ? out_height - 1 - iy : iy); + for (int ix = 0; ix < nxx; ++ix) { + p[ix] = uEx; + } + } + } + // high x extrapolation zone + if (max_ix < out_width - 1) { + for (int iy = min_iy; iy <= max_iy; ++iy) { + int xx0 = flip_x ? 0 : (max_ix + 1) * channels; + int nxx = (out_width - 1 - max_ix) * channels; + U* p = output + xx0 + + out_row_size * (int64)(flip_y ? out_height - 1 - iy : iy); + for (int ix = 0; ix < nxx; ++ix) { + p[ix] = uEx; + } + } + } + U* output_y_ptr = + output + + out_row_size * (int64)(flip_y ? out_height - 1 - min_iy : min_iy); + // interpolation zone + if (channels == 1) { + for (int y = min_iy; y <= max_iy; ++y) { + const int iy = y - min_iy; + const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const float ys_lerp = ys[iy].lerp; + const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; + const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; + for (int x = x0; x <= x1; ++x) { + const int ix = flip_x ? out_width - 1 - min_ix - x : x - min_ix; + const int64 xs_lower = xs[ix].lower; + const int64 xs_upper = xs[ix].upper; + const float xs_lerp = xs[ix].lerp; + + // Read channel 0. + const float top_left0(ys_input_lower_ptr[xs_lower]); + const float top_right0(ys_input_lower_ptr[xs_upper]); + const float bottom_left0(ys_input_upper_ptr[xs_lower]); + const float bottom_right0(ys_input_upper_ptr[xs_upper]); + + // Compute output. + float result0 = compute_lerp(top_left0, top_right0, bottom_left0, + bottom_right0, xs_lerp, ys_lerp); + output_y_ptr[x] = + cast_to(result0, min_val, max_val, u_min_val, u_max_val); + } + output_y_ptr = + flip_y ? output_y_ptr - out_row_size : output_y_ptr + out_row_size; + } + } else if (channels == 2) { + for (int y = min_iy; y <= max_iy; ++y) { + const int iy = y - min_iy; + const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const float ys_lerp = ys[iy].lerp; + const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; + const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; + for (int x = x0; x <= x1; ++x) { + const int ix = flip_x ? out_width - 1 - min_ix - x : x - min_ix; + const int64 xs_lower = xs[ix].lower; + const int64 xs_upper = xs[ix].upper; + const float xs_lerp = xs[ix].lerp; + + // Read channel 0. + const float top_left0(ys_input_lower_ptr[xs_lower + 0]); + const float top_right0(ys_input_lower_ptr[xs_upper + 0]); + const float bottom_left0(ys_input_upper_ptr[xs_lower + 0]); + const float bottom_right0(ys_input_upper_ptr[xs_upper + 0]); + + // Read channel 1. + const float top_left1(ys_input_lower_ptr[xs_lower + 1]); + const float top_right1(ys_input_lower_ptr[xs_upper + 1]); + const float bottom_left1(ys_input_upper_ptr[xs_lower + 1]); + const float bottom_right1(ys_input_upper_ptr[xs_upper + 1]); + + // Compute output. + float result0 = compute_lerp(top_left0, top_right0, bottom_left0, + bottom_right0, xs_lerp, ys_lerp); + float result1 = compute_lerp(top_left1, top_right1, bottom_left1, + bottom_right1, xs_lerp, ys_lerp); + output_y_ptr[x * 2 + 0] = + cast_to(result0, min_val, max_val, u_min_val, u_max_val); + output_y_ptr[x * 2 + 1] = + cast_to(result1, min_val, max_val, u_min_val, u_max_val); + } + output_y_ptr = + flip_y ? output_y_ptr - out_row_size : output_y_ptr + out_row_size; + } + } else if (channels == 3) { + for (int y = min_iy; y <= max_iy; ++y) { + const int iy = y - min_iy; + const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const float ys_lerp = ys[iy].lerp; + const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; + const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; + for (int x = x0; x <= x1; ++x) { + const int ix = flip_x ? out_width - 1 - min_ix - x : x - min_ix; + const int64 xs_lower = xs[ix].lower; + const int64 xs_upper = xs[ix].upper; + const float xs_lerp = xs[ix].lerp; + + // Read channel 0. + const float top_left0(ys_input_lower_ptr[xs_lower + 0]); + const float top_right0(ys_input_lower_ptr[xs_upper + 0]); + const float bottom_left0(ys_input_upper_ptr[xs_lower + 0]); + const float bottom_right0(ys_input_upper_ptr[xs_upper + 0]); + + // Read channel 1. + const float top_left1(ys_input_lower_ptr[xs_lower + 1]); + const float top_right1(ys_input_lower_ptr[xs_upper + 1]); + const float bottom_left1(ys_input_upper_ptr[xs_lower + 1]); + const float bottom_right1(ys_input_upper_ptr[xs_upper + 1]); + + // Read channel 2. + const float top_left2(ys_input_lower_ptr[xs_lower + 2]); + const float top_right2(ys_input_lower_ptr[xs_upper + 2]); + const float bottom_left2(ys_input_upper_ptr[xs_lower + 2]); + const float bottom_right2(ys_input_upper_ptr[xs_upper + 2]); + + // Compute output. + float result0 = compute_lerp(top_left0, top_right0, bottom_left0, + bottom_right0, xs_lerp, ys_lerp); + float result1 = compute_lerp(top_left1, top_right1, bottom_left1, + bottom_right1, xs_lerp, ys_lerp); + float result2 = compute_lerp(top_left2, top_right2, bottom_left2, + bottom_right2, xs_lerp, ys_lerp); + output_y_ptr[x * 3 + 0] = + cast_to(result0, min_val, max_val, u_min_val, u_max_val); + output_y_ptr[x * 3 + 1] = + cast_to(result1, min_val, max_val, u_min_val, u_max_val); + output_y_ptr[x * 3 + 2] = + cast_to(result2, min_val, max_val, u_min_val, u_max_val); + } + output_y_ptr = + flip_y ? output_y_ptr - out_row_size : output_y_ptr + out_row_size; + } + } else if (channels == 4) { + for (int y = min_iy; y <= max_iy; ++y) { + const int iy = y - min_iy; + const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const float ys_lerp = ys[iy].lerp; + const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; + const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; + for (int x = x0; x <= x1; ++x) { + const int ix = flip_x ? out_width - 1 - min_ix - x : x - min_ix; + const int64 xs_lower = xs[ix].lower; + const int64 xs_upper = xs[ix].upper; + const float xs_lerp = xs[ix].lerp; + + // Read channel 0. + const float top_left0(ys_input_lower_ptr[xs_lower + 0]); + const float top_right0(ys_input_lower_ptr[xs_upper + 0]); + const float bottom_left0(ys_input_upper_ptr[xs_lower + 0]); + const float bottom_right0(ys_input_upper_ptr[xs_upper + 0]); + + // Read channel 1. + const float top_left1(ys_input_lower_ptr[xs_lower + 1]); + const float top_right1(ys_input_lower_ptr[xs_upper + 1]); + const float bottom_left1(ys_input_upper_ptr[xs_lower + 1]); + const float bottom_right1(ys_input_upper_ptr[xs_upper + 1]); + + // Read channel 2. + const float top_left2(ys_input_lower_ptr[xs_lower + 2]); + const float top_right2(ys_input_lower_ptr[xs_upper + 2]); + const float bottom_left2(ys_input_upper_ptr[xs_lower + 2]); + const float bottom_right2(ys_input_upper_ptr[xs_upper + 2]); + + // Read channel 3. + const float top_left3(ys_input_lower_ptr[xs_lower + 3]); + const float top_right3(ys_input_lower_ptr[xs_upper + 3]); + const float bottom_left3(ys_input_upper_ptr[xs_lower + 3]); + const float bottom_right3(ys_input_upper_ptr[xs_upper + 3]); + + // Compute output. + float result0 = compute_lerp(top_left0, top_right0, bottom_left0, + bottom_right0, xs_lerp, ys_lerp); + float result1 = compute_lerp(top_left1, top_right1, bottom_left1, + bottom_right1, xs_lerp, ys_lerp); + float result2 = compute_lerp(top_left2, top_right2, bottom_left2, + bottom_right2, xs_lerp, ys_lerp); + float result3 = compute_lerp(top_left3, top_right3, bottom_left3, + bottom_right3, xs_lerp, ys_lerp); + output_y_ptr[x * 4 + 0] = + cast_to(result0, min_val, max_val, u_min_val, u_max_val); + output_y_ptr[x * 4 + 1] = + cast_to(result1, min_val, max_val, u_min_val, u_max_val); + output_y_ptr[x * 4 + 2] = + cast_to(result2, min_val, max_val, u_min_val, u_max_val); + output_y_ptr[x * 4 + 3] = + cast_to(result3, min_val, max_val, u_min_val, u_max_val); + } + output_y_ptr = + flip_y ? output_y_ptr - out_row_size : output_y_ptr + out_row_size; + } + } else { + for (int y = min_iy; y <= max_iy; ++y) { + const int iy = y - min_iy; + const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const float ys_lerp = ys[iy].lerp; + const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; + const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; + for (int x = x0; x <= x1; ++x) { + const int ix = flip_x ? out_width - 1 - min_ix - x : x - min_ix; + const int64 xs_lower = xs[ix].lower; + const int64 xs_upper = xs[ix].upper; + const float xs_lerp = xs[ix].lerp; + for (int ichan = 0; ichan < channels; ++ichan) { + const float top_left0(ys_input_lower_ptr[xs_lower + ichan]); + const float top_right0(ys_input_lower_ptr[xs_upper + ichan]); + const float bottom_left0(ys_input_upper_ptr[xs_lower + ichan]); + const float bottom_right0(ys_input_upper_ptr[xs_upper + ichan]); + float result0 = compute_lerp(top_left0, top_right0, bottom_left0, + bottom_right0, xs_lerp, ys_lerp); + output_y_ptr[x * channels + ichan] = + cast_to(result0, min_val, max_val, u_min_val, u_max_val); + } + } + output_y_ptr = + flip_y ? output_y_ptr - out_row_size : output_y_ptr + out_row_size; + } + } +} + +#ifdef __SSE4_1__ + +// +// The remaining code implements explicitly vectorized versions of a bilinear +// image resizer. +// Images with 1, 2, 3 or 4 channels are supported. +// The image resizer reads samples of type T and writes samples of type U. +// T and U can be any of the following: uint8, int8, uint16, int16, int32, +// Eigen::half, bfloat16 and float. +// There are separate codes for SSE4.1 and AVX2. Enabling AVX2 also enables +// FP16C instruction set, +// which contains instructions that convert between Eigen::half and float. The +// SSE4.1 code path emulates +// the FP16C instructions in software. +// + +// +// This class loads 4 pixels with n channels, converts to fp32 and packs +// the result into n SSE vector words. +// Input data type T must be one of uint8, int8, uint16, int16, int32, +// Eigen::half, bfloat16 or float. +// + +template +class VectorLoader { + public: +#ifdef __AVX2__ + // convert 8 packed words of type T to fp32. + // T must be one of uint8, int8, uint16, int16, int32, Eigen::half, bfloat16 + // or float. + __m256 to_fp32(__m256i raw); +#else + // convert 4 packed words of type T to fp32. + // T must be one of uint8, int8, uint16, int16, int32, Eigen::half, bfloat16 + // or float. + __m128 to_fp32(__m128i raw); +#endif + +#ifdef __AVX2__ + // pack 4 pixels with 1 channel, 2 channels and 3channels respectively in + // separate 128 bit lanes. + // input is stored in lower portion of 4 separate sse words, v0 through v3. + // output is stored in lower portion of v0. + void pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + // output is stored in lower portion of v0 and v1. + void pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + // output is stored in lower portion of v0, v1 and v2. + void pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); +#else + // pack 4 pixels with 1 channel, 2 channels and 3channels respectively. + // input is stored in lower portion of 4 separate sse words, v0 through v3. + // output is stored in lower portion of v0. + void pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + // output is stored in lower portion of v0 and v1. + void pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + // output is stored in lower portion of v0, v1 and v2. + void pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); +#endif + +#ifdef __AVX2__ + // extract right pixel for load1 and load4 cases. + __m256i extract_right_1ch(const __m256i left); + __m256i extract_right_2ch(const __m256i left); + __m256i extract_right_3ch(const __m256i left); + __m256i extract_right_4ch(const __m256i left); +#else + __m128i extract_right_1ch(const __m128i left); + __m128i extract_right_2ch(const __m128i left); + __m128i extract_right_3ch(const __m128i left); + __m128i extract_right_4ch(const __m128i left); +#endif + +#ifdef __AVX2__ + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 1 channel. + // load1 case, i.e. 4 left and right inputs are loaded with a single unaligned + // SSE load. + void load1_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m256* left0, __m256* right0); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 2 channels. + // load1 case, i.e. 4 left and right inputs are loaded with a single unaligned + // SSE load. + void load1_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m256* left0, __m256* left1, + __m256* right0, __m256* right1); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 3 channels. + // load1 case, i.e. 4 left and right inputs are loaded with a single unaligned + // SSE load. + void load1_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m256* left0, __m256* left1, + __m256* left2, __m256* right0, __m256* right1, __m256* right2); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 4 channels. + // load1 case, i.e. 4 left and right inputs are loaded with a single unaligned + // SSE load. + void load1_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m256* left0, __m256* left1, + __m256* left2, __m256* left3, __m256* right0, __m256* right1, + __m256* right2, __m256* right3); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 1 channel. + // load2 case, i.e. 4 left inputs are loaded with first SSE load and 4 right + // inputs are loaded with second SSE load. + void load2_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m256* left0, __m256* right0); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 2 channels. + // load2 case, i.e. 4 left inputs are loaded with first SSE load and 4 right + // inputs are loaded with second SSE load. + void load2_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m256* left0, __m256* left1, + __m256* right0, __m256* right1); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 3 channels. + // load2 case, i.e. 4 left inputs are loaded with first SSE load and 4 right + // inputs are loaded with second SSE load. + void load2_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m256* left0, __m256* left1, + __m256* left2, __m256* right0, __m256* right1, __m256* right2); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 4 channels. + // load2 case, i.e. 4 left inputs are loaded with first SSE load and 4 right + // inputs are loaded with second SSE load. + void load2_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m256* left0, __m256* left1, + __m256* left2, __m256* left3, __m256* right0, __m256* right1, + __m256* right2, __m256* right3); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 1 channel. + // load4 case, i.e. each pair of left and right inputs are loaded with a + // separate SSE load. + void load4_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256* left0, + __m256* right0); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 2 channels. + // load4 case, i.e. each pair of left and right inputs are loaded with a + // separate SSE load. + void load4_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256* left0, + __m256* left1, __m256* right0, __m256* right1); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 3 channels. + // load4 case, i.e. each pair of left and right inputs are loaded with a + // separate SSE load. + void load4_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256* left0, + __m256* left1, __m256* left2, __m256* right0, __m256* right1, + __m256* right2); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 4 channels. + // load4 case, i.e. each pair of left and right inputs are loaded with a + // separate SSE load. + void load4_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256* left0, + __m256* left1, __m256* left2, __m256* left3, __m256* right0, + __m256* right1, __m256* right2, __m256* right3); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 1 channel. + // load8 case, i.e. each input is loaded with a separate SSE load. + // 4 pixels, each with left and right input necessitates 8 separate SSE loads + // per input row. + void load8_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256* left0, + __m256* right0); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 2 channels. + // load8 case, i.e. each input is loaded with a separate SSE load. + // 4 pixels, each with left and right input necessitates 8 separate SSE loads + // per input row. + void load8_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256* left0, + __m256* left1, __m256* right0, __m256* right1); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 3 channels. + // load8 case, i.e. each input is loaded with a separate SSE load. + // 4 pixels, each with left and right input necessitates 8 separate SSE loads + // per input row. + void load8_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256* left0, + __m256* left1, __m256* left2, __m256* right0, __m256* right1, + __m256* right2); + // load top left and bottom left interpolation inputs into output argument + // left. + // load top right and bottom right interpolation inputs into output argument + // right. + // pixels have 4 channels. + // load8 case, i.e. each input is loaded with a separate SSE load. + // 4 pixels, each with left and right input necessitates 8 separate SSE loads + // per input row. + void load8_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256* left0, + __m256* left1, __m256* left2, __m256* left3, __m256* right0, + __m256* right1, __m256* right2, __m256* right3); +#else + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 1 channel. + // load1 case, i.e. all inputs for one input row are loaded with a single SSE + // load. + void load1_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m128* tl0, __m128* bl0, + __m128* tr0, __m128* br0); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 2 channels. + // load1 case, i.e. all inputs for one input row are loaded with a single SSE + // load. + void load1_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, + __m128* bl0, __m128* bl1, __m128* tr0, __m128* tr1, + __m128* br0, __m128* br1); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 3 channels. + // load1 case, i.e. all inputs for one input row are loaded with a single SSE + // load. + void load1_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, + __m128* tl2, __m128* bl0, __m128* bl1, __m128* bl2, + __m128* tr0, __m128* tr1, __m128* tr2, __m128* br0, + __m128* br1, __m128* br2); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 4 channels. + // load1 case, i.e. all inputs for one input row are loaded with a single SSE + // load. + void load1_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, + __m128* tl2, __m128* tl3, __m128* bl0, __m128* bl1, + __m128* bl2, __m128* bl3, __m128* tr0, __m128* tr1, + __m128* tr2, __m128* tr3, __m128* br0, __m128* br1, + __m128* br2, __m128* br3); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 1 channel. + // load2 case, i.e. left inputs are loaded with first SSE load, right inputs + // are loaded with second SSE load. + void load2_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m128* tl0, __m128* bl0, + __m128* tr0, __m128* br0); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 2 channels. + // load2 case, i.e. left inputs are loaded with first SSE load, right inputs + // are loaded with second SSE load. + void load2_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, + __m128* bl0, __m128* bl1, __m128* tr0, __m128* tr1, + __m128* br0, __m128* br1); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 3 channels. + // load2 case, i.e. left inputs are loaded with first SSE load, right inputs + // are loaded with second SSE load. + void load2_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, + __m128* tl2, __m128* bl0, __m128* bl1, __m128* bl2, + __m128* tr0, __m128* tr1, __m128* tr2, __m128* br0, + __m128* br1, __m128* br2); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 4 channels. + // load2 case, i.e. left inputs are loaded with first SSE load, right inputs + // are loaded with second SSE load. + void load2_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, + const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, + __m128* tl2, __m128* tl3, __m128* bl0, __m128* bl1, + __m128* bl2, __m128* bl3, __m128* tr0, __m128* tr1, + __m128* tr2, __m128* tr3, __m128* br0, __m128* br1, + __m128* br2, __m128* br3); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 1 channel. + // load4 case, i.e. left and right inputs are loaded with a separate SSE load + // for each pixel. + void load4_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128* tl0, + __m128* bl0, __m128* tr0, __m128* br0); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 2 channels. + // load4 case, i.e. left and right inputs are loaded with a separate SSE load + // for each pixel. + void load4_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128* tl0, + __m128* tl1, __m128* bl0, __m128* bl1, __m128* tr0, + __m128* tr1, __m128* br0, __m128* br1); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 3 channels. + // load4 case, i.e. left and right inputs are loaded with a separate SSE load + // for each pixel. + void load4_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128* tl0, + __m128* tl1, __m128* tl2, __m128* bl0, __m128* bl1, + __m128* bl2, __m128* tr0, __m128* tr1, __m128* tr2, + __m128* br0, __m128* br1, __m128* br2); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 4 channels. + // load4 case, i.e. left and right inputs are loaded with a separate SSE load + // for each pixel. + void load4_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128* tl0, + __m128* tl1, __m128* tl2, __m128* tl3, __m128* bl0, + __m128* bl1, __m128* bl2, __m128* bl3, __m128* tr0, + __m128* tr1, __m128* tr2, __m128* tr3, __m128* br0, + __m128* br1, __m128* br2, __m128* br3); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 1 channel. + // load8 case, i.e. left and right inputs are loaded with separate SSE loads + // for each pixel. + void load8_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128* tl0, + __m128* bl0, __m128* tr0, __m128* br0); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 2 channels. + // load8 case, i.e. left and right inputs are loaded with separate SSE loads + // for each pixel. + void load8_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128* tl0, + __m128* tl1, __m128* bl0, __m128* bl1, __m128* tr0, + __m128* tr1, __m128* br0, __m128* br1); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 3 channels. + // load8 case, i.e. left and right inputs are loaded with separate SSE loads + // for each pixel. + void load8_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128* tl0, + __m128* tl1, __m128* tl2, __m128* bl0, __m128* bl1, + __m128* bl2, __m128* tr0, __m128* tr1, __m128* tr2, + __m128* br0, __m128* br1, __m128* br2); + // load top left interpolation inputs into output argument tl. + // load bottom left interpolation inputs into output argument bl. + // load top right interpolation inputs into output argument tr. + // load bottom right interpolation inputs into output argument br. + // pixels have 4 channels. + // load8 case, i.e. left and right inputs are loaded with separate SSE loads + // for each pixel. + void load8_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128* tl0, + __m128* tl1, __m128* tl2, __m128* tl3, __m128* bl0, + __m128* bl1, __m128* bl2, __m128* bl3, __m128* tr0, + __m128* tr1, __m128* tr2, __m128* tr3, __m128* br0, + __m128* br1, __m128* br2, __m128* br3); +#endif + + // there is no method that packs 4 pixels with 4 channel into four sse words. + // nothing to do for this case, everything is already in the right position. + + private: +// helper methods +#ifdef __AVX2__ + // pack 4 pixels with 1, 2, 3 or 4 channels into lower portion of SSE vector + // word. + // works within SSE lanes. + // sizeof(sample_data_type) can be 1, 2 or 4 bytes. + void pack4_1b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_2b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_4b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_1b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_2b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_4b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_1b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_2b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_4b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); +// there is no pack4_xx_4ch functions because none is needed. +// all the bytes are loaded in the right spots for this case. +#else + // pack 4 pixels with 1, 2, 3 or 4 channels into lower portion of SSE vector + // word. + // sizeof(sample_data_type) can be 1, 2 or 4 bytes. + void pack4_1b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_2b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_4b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_1b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_2b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_4b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_1b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_2b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_4b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); +#endif +#ifdef __AVX2__ + __m256i extract_right_1b_(const __m256i left); + __m256i extract_right_2b_(const __m256i left); + __m256i extract_right_3b_(const __m256i left); + __m256i extract_right_4b_(const __m256i left); + __m256i extract_right_6b_(const __m256i left); + __m256i extract_right_8b_(const __m256i left); +#else + __m128i extract_right_1b_(const __m128i left); + __m128i extract_right_2b_(const __m128i left); + __m128i extract_right_3b_(const __m128i left); + __m128i extract_right_4b_(const __m128i left); + __m128i extract_right_6b_(const __m128i left); + __m128i extract_right_8b_(const __m128i left); +#endif +}; + +#ifdef __AVX2__ +template +void VectorLoader::pack4_1b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + *v3 = _mm256_slli_si256(*v3, 3); + __m256i and_mask = _mm256_setr_epi32(255, 0, 0, 0, 255, 0, 0, 0); + *v2 = _mm256_or_si256(*v3, + _mm256_slli_si256(_mm256_and_si256(and_mask, *v2), 2)); + *v1 = _mm256_or_si256(*v2, + _mm256_slli_si256(_mm256_and_si256(and_mask, *v1), 1)); + *v0 = _mm256_or_si256(*v1, _mm256_and_si256(and_mask, *v0)); +} +template +void VectorLoader::pack4_2b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + *v3 = _mm256_slli_si256(*v3, 6); + __m256i and_mask = _mm256_setr_epi32(65535, 0, 0, 0, 65535, 0, 0, 0); + *v2 = _mm256_or_si256(*v3, + _mm256_slli_si256(_mm256_and_si256(and_mask, *v2), 4)); + *v1 = _mm256_or_si256(*v2, + _mm256_slli_si256(_mm256_and_si256(and_mask, *v1), 2)); + *v0 = _mm256_or_si256(*v1, _mm256_and_si256(and_mask, *v0)); +} +template +void VectorLoader::pack4_4b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + *v3 = _mm256_slli_si256(*v3, 12); + __m256i and_mask = _mm256_setr_epi32(-1, 0, 0, 0, -1, 0, 0, 0); + *v2 = _mm256_or_si256(*v3, + _mm256_slli_si256(_mm256_and_si256(and_mask, *v2), 8)); + *v1 = _mm256_or_si256(*v2, + _mm256_slli_si256(_mm256_and_si256(and_mask, *v1), 4)); + *v0 = _mm256_or_si256(*v1, _mm256_and_si256(and_mask, *v0)); +} + +template +void VectorLoader::pack4_1b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + __m256i and_mask = _mm256_setr_epi32(65535, 0, 0, 0, 65535, 0, 0, 0); + *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), + _mm256_slli_si256(*v1, 2)); + *v1 = _mm256_or_si256(_mm256_and_si256(*v2, and_mask), + _mm256_slli_si256(*v3, 2)); +} +template +void VectorLoader::pack4_2b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + __m256i and_mask = _mm256_setr_epi32(-1, 0, 0, 0, -1, 0, 0, 0); + *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), + _mm256_slli_si256(*v1, 4)); + *v1 = _mm256_or_si256(_mm256_and_si256(*v2, and_mask), + _mm256_slli_si256(*v3, 4)); +} +template +void VectorLoader::pack4_4b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + __m256i and_mask = _mm256_setr_epi32(-1, -1, 0, 0, -1, -1, 0, 0); + *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), + _mm256_slli_si256(*v1, 8)); + *v1 = _mm256_or_si256(_mm256_and_si256(*v2, and_mask), + _mm256_slli_si256(*v3, 8)); +} + +template +void VectorLoader::pack4_1b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + __m256i and_mask = _mm256_setr_epi32(16777215, 0, 0, 0, 16777215, 0, 0, 0); + *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), + _mm256_slli_si256(*v1, 3)); + and_mask = _mm256_srli_si256(and_mask, 1); + *v1 = _mm256_or_si256(_mm256_and_si256(_mm256_srli_si256(*v1, 1), and_mask), + _mm256_slli_si256(*v2, 2)); + and_mask = _mm256_srli_si256(and_mask, 1); + *v2 = _mm256_or_si256(_mm256_and_si256(_mm256_srli_si256(*v2, 2), and_mask), + _mm256_slli_si256(*v3, 1)); +} +template +void VectorLoader::pack4_2b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + __m256i and_mask = _mm256_setr_epi32(-1, 65535, 0, 0, -1, 65535, 0, 0); + *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), + _mm256_slli_si256(*v1, 6)); + and_mask = _mm256_srli_si256(and_mask, 2); + *v1 = _mm256_or_si256(_mm256_and_si256(_mm256_srli_si256(*v1, 2), and_mask), + _mm256_slli_si256(*v2, 4)); + and_mask = _mm256_srli_si256(and_mask, 2); + *v2 = _mm256_or_si256(_mm256_and_si256(_mm256_srli_si256(*v2, 4), and_mask), + _mm256_slli_si256(*v3, 2)); +} +template +void VectorLoader::pack4_4b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + __m256i and_mask = _mm256_setr_epi32(-1, -1, -1, 0, -1, -1, -1, 0); + *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), + _mm256_slli_si256(*v1, 12)); + and_mask = _mm256_srli_si256(and_mask, 4); + *v1 = _mm256_or_si256(_mm256_and_si256(_mm256_srli_si256(*v1, 4), and_mask), + _mm256_slli_si256(*v2, 8)); + and_mask = _mm256_srli_si256(and_mask, 4); + *v2 = _mm256_or_si256(_mm256_and_si256(_mm256_srli_si256(*v2, 8), and_mask), + _mm256_slli_si256(*v3, 4)); +} + +template <> +void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_1b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_1b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_4b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_4b_1ch_(v0, v1, v2, v3); +} + +template <> +void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_1b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_1b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_4b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_4b_2ch_(v0, v1, v2, v3); +} + +template <> +void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_1b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_1b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_4b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_2b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, + __m256i* v3) { + pack4_4b_3ch_(v0, v1, v2, v3); +} +#else +template +void VectorLoader::pack4_1b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + *v3 = _mm_slli_si128(*v3, 3); + __m128i and_mask = _mm_setr_epi32(255, 0, 0, 0); + *v2 = _mm_or_si128(*v3, _mm_slli_si128(_mm_and_si128(and_mask, *v2), 2)); + *v1 = _mm_or_si128(*v2, _mm_slli_si128(_mm_and_si128(and_mask, *v1), 1)); + *v0 = _mm_or_si128(*v1, _mm_and_si128(and_mask, *v0)); +} +template +void VectorLoader::pack4_2b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + *v3 = _mm_slli_si128(*v3, 6); + __m128i and_mask = _mm_setr_epi32(65535, 0, 0, 0); + *v2 = _mm_or_si128(*v3, _mm_slli_si128(_mm_and_si128(and_mask, *v2), 4)); + *v1 = _mm_or_si128(*v2, _mm_slli_si128(_mm_and_si128(and_mask, *v1), 2)); + *v0 = _mm_or_si128(*v1, _mm_and_si128(and_mask, *v0)); +} +template +void VectorLoader::pack4_4b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + *v3 = _mm_slli_si128(*v3, 12); + __m128i and_mask = _mm_setr_epi32(-1, 0, 0, 0); + *v2 = _mm_or_si128(*v3, _mm_slli_si128(_mm_and_si128(and_mask, *v2), 8)); + *v1 = _mm_or_si128(*v2, _mm_slli_si128(_mm_and_si128(and_mask, *v1), 4)); + *v0 = _mm_or_si128(*v1, _mm_and_si128(and_mask, *v0)); +} +template +void VectorLoader::pack4_1b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + __m128i and_mask = _mm_setr_epi32(65535, 0, 0, 0); + *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 2)); + *v1 = _mm_or_si128(_mm_and_si128(*v2, and_mask), _mm_slli_si128(*v3, 2)); +} +template +void VectorLoader::pack4_2b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + __m128i and_mask = _mm_setr_epi32(-1, 0, 0, 0); + *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 4)); + *v1 = _mm_or_si128(_mm_and_si128(*v2, and_mask), _mm_slli_si128(*v3, 4)); +} +template +void VectorLoader::pack4_4b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + __m128i and_mask = _mm_setr_epi32(-1, -1, 0, 0); + *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 8)); + *v1 = _mm_or_si128(_mm_and_si128(*v2, and_mask), _mm_slli_si128(*v3, 8)); +} +template +void VectorLoader::pack4_1b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + __m128i and_mask = _mm_setr_epi32(16777215, 0, 0, 0); + *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 3)); + and_mask = _mm_srli_si128(and_mask, 1); + *v1 = _mm_or_si128(_mm_and_si128(_mm_srli_si128(*v1, 1), and_mask), + _mm_slli_si128(*v2, 2)); + and_mask = _mm_srli_si128(and_mask, 1); + *v2 = _mm_or_si128(_mm_and_si128(_mm_srli_si128(*v2, 2), and_mask), + _mm_slli_si128(*v3, 1)); +} +template +void VectorLoader::pack4_2b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + __m128i and_mask = _mm_setr_epi32(-1, 65535, 0, 0); + *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 6)); + and_mask = _mm_srli_si128(and_mask, 2); + *v1 = _mm_or_si128(_mm_and_si128(_mm_srli_si128(*v1, 2), and_mask), + _mm_slli_si128(*v2, 4)); + and_mask = _mm_srli_si128(and_mask, 2); + *v2 = _mm_or_si128(_mm_and_si128(_mm_srli_si128(*v2, 4), and_mask), + _mm_slli_si128(*v3, 2)); +} +template +void VectorLoader::pack4_4b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + __m128i and_mask = _mm_setr_epi32(-1, -1, -1, 0); + *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 12)); + and_mask = _mm_srli_si128(and_mask, 4); + *v1 = _mm_or_si128(_mm_and_si128(_mm_srli_si128(*v1, 4), and_mask), + _mm_slli_si128(*v2, 8)); + and_mask = _mm_srli_si128(and_mask, 4); + *v2 = _mm_or_si128(_mm_and_si128(_mm_srli_si128(*v2, 8), and_mask), + _mm_slli_si128(*v3, 4)); +} + +template <> +void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_1b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_1b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_4b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_1ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_4b_1ch_(v0, v1, v2, v3); +} + +template <> +void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_1b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_1b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_4b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_2ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_4b_2ch_(v0, v1, v2, v3); +} + +template <> +void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_1b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_1b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_4b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_2b_3ch_(v0, v1, v2, v3); +} +template <> +void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, + __m128i* v3) { + pack4_4b_3ch_(v0, v1, v2, v3); +} +#endif + +#ifdef __AVX2__ +template <> +__m256i VectorLoader::extract_right_1ch(const __m256i left) { + return extract_right_1b_(left); +} +template <> +__m256i VectorLoader::extract_right_1ch(const __m256i left) { + return extract_right_1b_(left); +} +template <> +__m256i VectorLoader::extract_right_1ch(const __m256i left) { + return extract_right_2b_(left); +} +template <> +__m256i VectorLoader::extract_right_1ch(const __m256i left) { + return extract_right_2b_(left); +} +template <> +__m256i VectorLoader::extract_right_1ch(const __m256i left) { + return extract_right_4b_(left); +} +template <> +__m256i VectorLoader::extract_right_1ch(const __m256i left) { + return extract_right_2b_(left); +} +template <> +__m256i VectorLoader::extract_right_1ch(const __m256i left) { + return extract_right_2b_(left); +} +template <> +__m256i VectorLoader::extract_right_1ch(const __m256i left) { + return extract_right_4b_(left); +} + +template <> +__m256i VectorLoader::extract_right_2ch(const __m256i left) { + return extract_right_2b_(left); +} +template <> +__m256i VectorLoader::extract_right_2ch(const __m256i left) { + return extract_right_2b_(left); +} +template <> +__m256i VectorLoader::extract_right_2ch(const __m256i left) { + return extract_right_4b_(left); +} +template <> +__m256i VectorLoader::extract_right_2ch(const __m256i left) { + return extract_right_4b_(left); +} +template <> +__m256i VectorLoader::extract_right_2ch(const __m256i left) { + return extract_right_8b_(left); +} +template <> +__m256i VectorLoader::extract_right_2ch(const __m256i left) { + return extract_right_4b_(left); +} +template <> +__m256i VectorLoader::extract_right_2ch(const __m256i left) { + return extract_right_4b_(left); +} +template <> +__m256i VectorLoader::extract_right_2ch(const __m256i left) { + return extract_right_8b_(left); +} + +template <> +__m256i VectorLoader::extract_right_3ch(const __m256i left) { + return extract_right_3b_(left); +} +template <> +__m256i VectorLoader::extract_right_3ch(const __m256i left) { + return extract_right_3b_(left); +} +template <> +__m256i VectorLoader::extract_right_3ch(const __m256i left) { + return extract_right_6b_(left); +} +template <> +__m256i VectorLoader::extract_right_3ch(const __m256i left) { + return extract_right_6b_(left); +} +template <> +__m256i VectorLoader::extract_right_3ch(const __m256i left) { + assert(false); +} +template <> +__m256i VectorLoader::extract_right_3ch(const __m256i left) { + return extract_right_6b_(left); +} +template <> +__m256i VectorLoader::extract_right_3ch(const __m256i left) { + return extract_right_6b_(left); +} +template <> +__m256i VectorLoader::extract_right_3ch(const __m256i left) { + assert(false); +} + +template <> +__m256i VectorLoader::extract_right_4ch(const __m256i left) { + return extract_right_4b_(left); +} +template <> +__m256i VectorLoader::extract_right_4ch(const __m256i left) { + return extract_right_4b_(left); +} +template <> +__m256i VectorLoader::extract_right_4ch(const __m256i left) { + return extract_right_8b_(left); +} +template <> +__m256i VectorLoader::extract_right_4ch(const __m256i left) { + return extract_right_8b_(left); +} +template <> +__m256i VectorLoader::extract_right_4ch(const __m256i left) { + assert(false); +} +template <> +__m256i VectorLoader::extract_right_4ch(const __m256i left) { + return extract_right_8b_(left); +} +template <> +__m256i VectorLoader::extract_right_4ch(const __m256i left) { + return extract_right_8b_(left); +} +template <> +__m256i VectorLoader::extract_right_4ch(const __m256i left) { + assert(false); +} +#else +template <> +__m128i VectorLoader::extract_right_1ch(const __m128i left) { + return extract_right_1b_(left); +} +template <> +__m128i VectorLoader::extract_right_1ch(const __m128i left) { + return extract_right_1b_(left); +} +template <> +__m128i VectorLoader::extract_right_1ch(const __m128i left) { + return extract_right_2b_(left); +} +template <> +__m128i VectorLoader::extract_right_1ch(const __m128i left) { + return extract_right_2b_(left); +} +template <> +__m128i VectorLoader::extract_right_1ch(const __m128i left) { + return extract_right_4b_(left); +} +template <> +__m128i VectorLoader::extract_right_1ch(const __m128i left) { + return extract_right_2b_(left); +} +template <> +__m128i VectorLoader::extract_right_1ch(const __m128i left) { + return extract_right_2b_(left); +} +template <> +__m128i VectorLoader::extract_right_1ch(const __m128i left) { + return extract_right_4b_(left); +} + +template <> +__m128i VectorLoader::extract_right_2ch(const __m128i left) { + return extract_right_2b_(left); +} +template <> +__m128i VectorLoader::extract_right_2ch(const __m128i left) { + return extract_right_2b_(left); +} +template <> +__m128i VectorLoader::extract_right_2ch(const __m128i left) { + return extract_right_4b_(left); +} +template <> +__m128i VectorLoader::extract_right_2ch(const __m128i left) { + return extract_right_4b_(left); +} +template <> +__m128i VectorLoader::extract_right_2ch(const __m128i left) { + return extract_right_8b_(left); +} +template <> +__m128i VectorLoader::extract_right_2ch(const __m128i left) { + return extract_right_4b_(left); +} +template <> +__m128i VectorLoader::extract_right_2ch(const __m128i left) { + return extract_right_4b_(left); +} +template <> +__m128i VectorLoader::extract_right_2ch(const __m128i left) { + return extract_right_8b_(left); +} + +template <> +__m128i VectorLoader::extract_right_3ch(const __m128i left) { + return extract_right_3b_(left); +} +template <> +__m128i VectorLoader::extract_right_3ch(const __m128i left) { + return extract_right_3b_(left); +} +template <> +__m128i VectorLoader::extract_right_3ch(const __m128i left) { + return extract_right_6b_(left); +} +template <> +__m128i VectorLoader::extract_right_3ch(const __m128i left) { + return extract_right_6b_(left); +} +template <> +__m128i VectorLoader::extract_right_3ch(const __m128i left) { + assert(false); +} +template <> +__m128i VectorLoader::extract_right_3ch(const __m128i left) { + return extract_right_6b_(left); +} +template <> +__m128i VectorLoader::extract_right_3ch(const __m128i left) { + return extract_right_6b_(left); +} +template <> +__m128i VectorLoader::extract_right_3ch(const __m128i left) { + assert(false); +} + +template <> +__m128i VectorLoader::extract_right_4ch(const __m128i left) { + return extract_right_4b_(left); +} +template <> +__m128i VectorLoader::extract_right_4ch(const __m128i left) { + return extract_right_4b_(left); +} +template <> +__m128i VectorLoader::extract_right_4ch(const __m128i left) { + return extract_right_8b_(left); +} +template <> +__m128i VectorLoader::extract_right_4ch(const __m128i left) { + return extract_right_8b_(left); +} +template <> +__m128i VectorLoader::extract_right_4ch(const __m128i left) { + assert(false); +} +template <> +__m128i VectorLoader::extract_right_4ch(const __m128i left) { + return extract_right_8b_(left); +} +template <> +__m128i VectorLoader::extract_right_4ch(const __m128i left) { + return extract_right_8b_(left); +} +template <> +__m128i VectorLoader::extract_right_4ch(const __m128i left) { + assert(false); +} +#endif + +#ifdef __AVX2__ +template <> +__m256 VectorLoader::to_fp32(__m256i raw) { + raw = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_cvtepu8_epi32(_mm256_castsi256_si128(raw))), + _mm_cvtepu8_epi32(_mm256_extractf128_si256(raw, 1)), 1); + return _mm256_cvtepi32_ps(raw); +} +template <> +__m256 VectorLoader::to_fp32(__m256i raw) { + raw = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_cvtepi8_epi32(_mm256_castsi256_si128(raw))), + _mm_cvtepi8_epi32(_mm256_extractf128_si256(raw, 1)), 1); + return _mm256_cvtepi32_ps(raw); +} +template <> +__m256 VectorLoader::to_fp32(__m256i raw) { + raw = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_cvtepu16_epi32(_mm256_castsi256_si128(raw))), + _mm_cvtepu16_epi32(_mm256_extractf128_si256(raw, 1)), 1); + return _mm256_cvtepi32_ps(raw); +} +template <> +__m256 VectorLoader::to_fp32(__m256i raw) { + raw = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_cvtepi16_epi32(_mm256_castsi256_si128(raw))), + _mm_cvtepi16_epi32(_mm256_extractf128_si256(raw, 1)), 1); + return _mm256_cvtepi32_ps(raw); +} +template <> +__m256 VectorLoader::to_fp32(__m256i raw) { + return _mm256_cvtepi32_ps(raw); +} +template <> +__m256 VectorLoader::to_fp32(__m256i raw) { + return _mm256_insertf128_ps( + _mm256_castps128_ps256(_mm_cvtph_ps(_mm256_castsi256_si128(raw))), + _mm_cvtph_ps(_mm256_extractf128_si256(raw, 1)), 1); +} +template <> +__m256 VectorLoader::to_fp32(__m256i raw) { + // bfloat16 is essentially fp32 with mantissa truncated from 23 to 7 bits. + // can convert with << 16, which we fuse with initial shuffle into epi32 + // positions. + __m256i shuf_hi32 = _mm256_setr_epi8( + -128, -128, 0, 1, -128, -128, 2, 3, -128, -128, 4, 5, -128, -128, 6, 7, + -128, -128, 0, 1, -128, -128, 2, 3, -128, -128, 4, 5, -128, -128, 6, 7); + return _mm256_castsi256_ps(_mm256_shuffle_epi8(raw, shuf_hi32)); +} +template <> +__m256 VectorLoader::to_fp32(__m256i raw) { + return _mm256_castsi256_ps(raw); +} +#else +template <> +__m128 VectorLoader::to_fp32(__m128i raw) { + return _mm_cvtepi32_ps(_mm_cvtepu8_epi32(raw)); +} +template <> +__m128 VectorLoader::to_fp32(__m128i raw) { + return _mm_cvtepi32_ps(_mm_cvtepi8_epi32(raw)); +} +template <> +__m128 VectorLoader::to_fp32(__m128i raw) { + return _mm_cvtepi32_ps(_mm_cvtepu16_epi32(raw)); +} +template <> +__m128 VectorLoader::to_fp32(__m128i raw) { + return _mm_cvtepi32_ps(_mm_cvtepi16_epi32(raw)); +} +template <> +__m128 VectorLoader::to_fp32(__m128i raw) { + return _mm_cvtepi32_ps(raw); +} +template <> +__m128 VectorLoader::to_fp32(__m128i raw) { +#ifdef __F16C__ + return _mm_cvtph_ps(raw); +#else + // It is fairly trivial to convert from fp16 to fp32. + // The formats are defined as follows: + // + // fp16 :: 15=sign_bit, 14-10=exponent, 9-0=mantissa :: exp zero offset is 15 + // :: exponent of -15 (all 0) and +16 (all 1) are special numbers. + // fp32 :: 31=sign_bit, 30-23=exponent, 22-0=mantissa :: exp zero offset is + // 127 + // :: exponent of -127 (all 0) and +128 (all 1) are special numbers. + // + // Assuming the fp16 values is stored in the lower 16 bits of an int32 + // 'fp16_val'. + // + // fp16_mantissa = fp16_val & (2^10-1) + // fp32_mantissa = fp16_mantissa << 13 + // + // The exponent is a little trickier. + // For normal numbers, the following works: + // fp16_exponent_with_10bit_left_shift = (fp16_val & ((2^5-1)<<10)) + // fp16_exponent_at_msb = fp16_exponent_with_10bit_left_shift << 17 + // The next line shifts in 1's from msb + // fp16_exponent_at_fp32_position = fp16_exponent_at_msb >> 4 + // The next line flips the 3 bits from [msb-1,msb-4] + // fp32_exponent = fp16_exponent_at_fp32_position ^ (7 << 27) + // This breaks for subnormals, nan and infinity. + // The only thing that breaks is the 3bit bit flip, which should + // happen for normal numbers, but should not happen otherwise. + // Since the bit flip can be done with an XOR of all 1's, we + // can make this happen by turning the XOR mask to all zeros + // when the fp16_exponent is either 0 or 31. + // + // ..move 16-bit input words to lower part of 32-bit positions. + __m128i shuf_lo32 = _mm_setr_epi8(0, 1, -128, -128, 2, 3, -128, -128, 4, 5, + -128, -128, 6, 7, -128, -128); + __m128i fp16_val = _mm_shuffle_epi8(raw, shuf_lo32); + // ..extract sign bit + __m128i fp32_sign = + _mm_slli_epi32(_mm_and_si128(fp16_val, _mm_set1_epi32(32768)), 16); + // ..extract fp16_mantissa and shift + __m128i fp16_mantissa = _mm_and_si128(fp16_val, _mm_set1_epi32(1023)); + __m128i fp32_mantissa = _mm_slli_epi32(fp16_mantissa, 13); + // ..extract fp16 exponent shifted 10bits to the left + __m128i fp16_exponent_sl10 = _mm_and_si128(fp16_val, _mm_set1_epi32(31744)); + __m128i fp16_exponent_all1_mask = + _mm_cmpeq_epi32(fp16_exponent_sl10, _mm_set1_epi32(31 << 10)); + __m128i fp16_exponent_all0_mask = + _mm_cmpeq_epi32(fp16_exponent_sl10, _mm_setzero_si128()); + __m128i fp16_denormal_mask = + _mm_or_si128(fp16_exponent_all0_mask, fp16_exponent_all1_mask); + __m128i fp32_exponent_before_xor = + _mm_and_si128(_mm_set1_epi32(2139095040), + _mm_srai_epi32(_mm_slli_epi32(fp16_exponent_sl10, 17), 4)); + __m128i fp32_exponent_xor_mask = + _mm_andnot_si128(fp16_denormal_mask, _mm_set1_epi32(7 << 27)); + __m128i fp32_exponent = + _mm_xor_si128(fp32_exponent_xor_mask, fp32_exponent_before_xor); + // ..or everything into one word + __m128i fp32_val = + _mm_or_si128(_mm_or_si128(fp32_sign, fp32_exponent), fp32_mantissa); + return _mm_castsi128_ps(fp32_val); +#endif +} +template <> +__m128 VectorLoader::to_fp32(__m128i raw) { + // bfloat16 is essentially fp32 with mantissa truncated from 23 to 7 bits. + // can convert with << 16, which we fuse with initial shuffle into epi32 + // positions. + __m128i shuf_hi32 = _mm_setr_epi8(-128, -128, 0, 1, -128, -128, 2, 3, -128, + -128, 4, 5, -128, -128, 6, 7); + return _mm_castsi128_ps(_mm_shuffle_epi8(raw, shuf_hi32)); +} +template <> +__m128 VectorLoader::to_fp32(__m128i raw) { + return _mm_castsi128_ps(raw); +} +#endif + +#ifdef __AVX2__ +template +__m256i VectorLoader::extract_right_1b_(const __m256i left) { + return _mm256_srli_si256(left, 1); +} +template +__m256i VectorLoader::extract_right_2b_(const __m256i left) { + return _mm256_srli_si256(left, 2); +} +template +__m256i VectorLoader::extract_right_3b_(const __m256i left) { + return _mm256_srli_si256(left, 3); +} +template +__m256i VectorLoader::extract_right_4b_(const __m256i left) { + return _mm256_srli_si256(left, 4); +} +template +__m256i VectorLoader::extract_right_6b_(const __m256i left) { + return _mm256_srli_si256(left, 6); +} +template +__m256i VectorLoader::extract_right_8b_(const __m256i left) { + return _mm256_srli_si256(left, 8); +} +#else +template +__m128i VectorLoader::extract_right_1b_(const __m128i left) { + return _mm_srli_si128(left, 1); +} +template +__m128i VectorLoader::extract_right_2b_(const __m128i left) { + return _mm_srli_si128(left, 2); +} +template +__m128i VectorLoader::extract_right_3b_(const __m128i left) { + return _mm_srli_si128(left, 3); +} +template +__m128i VectorLoader::extract_right_4b_(const __m128i left) { + return _mm_srli_si128(left, 4); +} +template +__m128i VectorLoader::extract_right_6b_(const __m128i left) { + return _mm_srli_si128(left, 6); +} +template +__m128i VectorLoader::extract_right_8b_(const __m128i left) { + return _mm_srli_si128(left, 8); +} +#endif + +#ifdef __AVX2__ +template +void VectorLoader::load1_1ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m256* left0, __m256* right0) { + __m256i raw = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + *left0 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[0]))); + *right0 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[1]))); +} +template +void VectorLoader::load1_2ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m256* left0, __m256* left1, __m256* right0, + __m256* right1) { + __m256i raw = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + *left0 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[0]))); + *left1 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[1]))); + *right0 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[2]))); + *right1 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[3]))); +} +template +void VectorLoader::load1_3ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m256* left0, __m256* left1, __m256* left2, + __m256* right0, __m256* right1, + __m256* right2) { + __m256i raw = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + *left0 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[0]))); + *left1 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[1]))); + *left2 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[2]))); + *right0 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[3]))); + *right1 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[4]))); + *right2 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[5]))); +} +template +void VectorLoader::load1_4ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m256* left0, __m256* left1, __m256* left2, + __m256* left3, __m256* right0, __m256* right1, + __m256* right2, __m256* right3) { + __m256i raw = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + *left0 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[0]))); + *left1 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[1]))); + *left2 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[2]))); + *left3 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[3]))); + *right0 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[4]))); + *right1 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[5]))); + *right2 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[6]))); + *right3 = to_fp32( + _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[7]))); +} +template +void VectorLoader::load2_1ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m256* left0, __m256* right0) { + __m256i raw1 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i raw2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 1)), 1); + __m256i mask = _mm256_broadcastsi128_si256(shuffle_masks[0]); + *left0 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right0 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); +} +template +void VectorLoader::load2_2ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m256* left0, __m256* left1, __m256* right0, + __m256* right1) { + __m256i raw1 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i raw2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 2)), 1); + __m256i mask = _mm256_broadcastsi128_si256(shuffle_masks[0]); + *left0 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right0 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); + mask = _mm256_broadcastsi128_si256(shuffle_masks[1]); + *left1 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right1 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); +} +template +void VectorLoader::load2_3ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m256* left0, __m256* left1, __m256* left2, + __m256* right0, __m256* right1, + __m256* right2) { + __m256i raw1 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i raw2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 3)), 1); + __m256i mask = _mm256_broadcastsi128_si256(shuffle_masks[0]); + *left0 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right0 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); + mask = _mm256_broadcastsi128_si256(shuffle_masks[1]); + *left1 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right1 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); + mask = _mm256_broadcastsi128_si256(shuffle_masks[2]); + *left2 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right2 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); +} +template +void VectorLoader::load2_4ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m256* left0, __m256* left1, __m256* left2, + __m256* left3, __m256* right0, __m256* right1, + __m256* right2, __m256* right3) { + __m256i raw1 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i raw2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 4))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 4)), 1); + __m256i mask = _mm256_broadcastsi128_si256(shuffle_masks[0]); + *left0 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right0 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); + mask = _mm256_broadcastsi128_si256(shuffle_masks[1]); + *left1 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right1 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); + mask = _mm256_broadcastsi128_si256(shuffle_masks[2]); + *left2 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right2 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); + mask = _mm256_broadcastsi128_si256(shuffle_masks[3]); + *left3 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); + *right3 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); +} +template +void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m256* left0, __m256* right0) { + __m256i l0 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i r0 = extract_right_1ch(l0); + __m256i l1, r1; + if (offset1 == offset0) { + l1 = l0; + r1 = r0; + } else { + l1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + r1 = extract_right_1ch(l1); + } + __m256i l2, r2; + if (offset2 == offset1) { + l2 = l1; + r2 = r1; + } else { + l2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + r2 = extract_right_1ch(l2); + } + __m256i l3, r3; + if (offset3 == offset2) { + l3 = l2; + r3 = r2; + } else { + l3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + r3 = extract_right_1ch(l3); + } + pack_1ch(&l0, &l1, &l2, &l3); + *left0 = to_fp32(l0); + pack_1ch(&r0, &r1, &r2, &r3); + *right0 = to_fp32(r0); +} +template +void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m256* left0, __m256* left1, + __m256* right0, __m256* right1) { + __m256i l0 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i r0 = extract_right_2ch(l0); + __m256i l1, r1; + if (offset1 == offset0) { + l1 = l0; + r1 = r0; + } else { + l1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + r1 = extract_right_2ch(l1); + } + __m256i l2, r2; + if (offset2 == offset1) { + l2 = l1; + r2 = r1; + } else { + l2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + r2 = extract_right_2ch(l2); + } + __m256i l3, r3; + if (offset3 == offset2) { + l3 = l2; + r3 = r2; + } else { + l3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + r3 = extract_right_2ch(l3); + } + pack_2ch(&l0, &l1, &l2, &l3); + *left0 = to_fp32(l0); + *left1 = to_fp32(l1); + pack_2ch(&r0, &r1, &r2, &r3); + *right0 = to_fp32(r0); + *right1 = to_fp32(r1); +} +template +void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m256* left0, __m256* left1, + __m256* left2, __m256* right0, __m256* right1, + __m256* right2) { + __m256i l0 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i r0 = extract_right_3ch(l0); + __m256i l1, r1; + if (offset1 == offset0) { + l1 = l0; + r1 = r0; + } else { + l1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + r1 = extract_right_3ch(l1); + } + __m256i l2, r2; + if (offset2 == offset1) { + l2 = l1; + r2 = r1; + } else { + l2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + r2 = extract_right_3ch(l2); + } + __m256i l3, r3; + if (offset3 == offset2) { + l3 = l2; + r3 = r2; + } else { + l3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + r3 = extract_right_3ch(l3); + } + pack_3ch(&l0, &l1, &l2, &l3); + *left0 = to_fp32(l0); + *left1 = to_fp32(l1); + *left2 = to_fp32(l2); + pack_3ch(&r0, &r1, &r2, &r3); + *right0 = to_fp32(r0); + *right1 = to_fp32(r1); + *right2 = to_fp32(r2); +} +template +void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m256* left0, __m256* left1, + __m256* left2, __m256* left3, __m256* right0, + __m256* right1, __m256* right2, + __m256* right3) { + __m256i l0 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i r0 = extract_right_4ch(l0); + __m256i l1, r1; + if (offset1 == offset0) { + l1 = l0; + r1 = r0; + } else { + l1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + r1 = extract_right_4ch(l1); + } + __m256i l2, r2; + if (offset2 == offset1) { + l2 = l1; + r2 = r1; + } else { + l2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + r2 = extract_right_4ch(l2); + } + __m256i l3, r3; + if (offset3 == offset2) { + l3 = l2; + r3 = r2; + } else { + l3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + r3 = extract_right_4ch(l3); + } + *left0 = to_fp32(l0); + *left1 = to_fp32(l1); + *left2 = to_fp32(l2); + *left3 = to_fp32(l3); + *right0 = to_fp32(r0); + *right1 = to_fp32(r1); + *right2 = to_fp32(r2); + *right3 = to_fp32(r3); +} +template +void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m256* left0, __m256* right0) { + __m256i l0 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i r0 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 1)), 1); + __m256i l1, r1; + if (offset1 == offset0) { + l1 = l0; + r1 = r0; + } else { + l1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + r1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 1)), 1); + } + __m256i l2, r2; + if (offset2 == offset1) { + l2 = l1; + r2 = r1; + } else { + l2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + r2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 1)), 1); + } + __m256i l3, r3; + if (offset3 == offset2) { + l3 = l2; + r3 = r2; + } else { + l3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + r3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 1)), 1); + } + pack_1ch(&l0, &l1, &l2, &l3); + *left0 = to_fp32(l0); + pack_1ch(&r0, &r1, &r2, &r3); + *right0 = to_fp32(r0); +} +template +void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m256* left0, __m256* left1, + __m256* right0, __m256* right1) { + __m256i l0 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i r0 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 2)), 1); + __m256i l1, r1; + if (offset1 == offset0) { + l1 = l0; + r1 = r0; + } else { + l1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + r1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 2)), 1); + } + __m256i l2, r2; + if (offset2 == offset1) { + l2 = l1; + r2 = r1; + } else { + l2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + r2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 2)), 1); + } + __m256i l3, r3; + if (offset3 == offset2) { + l3 = l2; + r3 = r2; + } else { + l3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + r3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 2)), 1); + } + pack_2ch(&l0, &l1, &l2, &l3); + *left0 = to_fp32(l0); + *left1 = to_fp32(l1); + pack_2ch(&r0, &r1, &r2, &r3); + *right0 = to_fp32(r0); + *right1 = to_fp32(r1); +} +template +void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m256* left0, __m256* left1, + __m256* left2, __m256* right0, __m256* right1, + __m256* right2) { + __m256i l0 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i r0 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 3)), 1); + __m256i l1, r1; + if (offset1 == offset0) { + l1 = l0; + r1 = r0; + } else { + l1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + r1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 3)), 1); + } + __m256i l2, r2; + if (offset2 == offset1) { + l2 = l1; + r2 = r1; + } else { + l2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + r2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 3)), 1); + } + __m256i l3, r3; + if (offset3 == offset2) { + l3 = l2; + r3 = r2; + } else { + l3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + r3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 3)), 1); + } + pack_3ch(&l0, &l1, &l2, &l3); + *left0 = to_fp32(l0); + *left1 = to_fp32(l1); + *left2 = to_fp32(l2); + pack_3ch(&r0, &r1, &r2, &r3); + *right0 = to_fp32(r0); + *right1 = to_fp32(r1); + *right2 = to_fp32(r2); +} +template +void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m256* left0, __m256* left1, + __m256* left2, __m256* left3, __m256* right0, + __m256* right1, __m256* right2, + __m256* right3) { + __m256i l0 = _mm256_insertf128_si256( + _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + __m256i r0 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 4))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 4)), 1); + __m256i l1, r1; + if (offset1 == offset0) { + l1 = l0; + r1 = r0; + } else { + l1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + r1 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 4))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 4)), 1); + } + __m256i l2, r2; + if (offset2 == offset1) { + l2 = l1; + r2 = r1; + } else { + l2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + r2 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 4))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 4)), 1); + } + __m256i l3, r3; + if (offset3 == offset2) { + l3 = l2; + r3 = r2; + } else { + l3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + r3 = _mm256_insertf128_si256( + _mm256_castsi128_si256( + _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 4))), + _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 4)), 1); + } + *left0 = to_fp32(l0); + *left1 = to_fp32(l1); + *left2 = to_fp32(l2); + *left3 = to_fp32(l3); + *right0 = to_fp32(r0); + *right1 = to_fp32(r1); + *right2 = to_fp32(r2); + *right3 = to_fp32(r3); +} +#else +template +void VectorLoader::load1_1ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m128* tl0, __m128* bl0, __m128* tr0, + __m128* br0) { + __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); +} +template +void VectorLoader::load1_2ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m128* tl0, __m128* tl1, __m128* bl0, + __m128* bl1, __m128* tr0, __m128* tr1, + __m128* br0, __m128* br1) { + __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); +} +template +void VectorLoader::load1_3ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m128* tl0, __m128* tl1, __m128* tl2, + __m128* bl0, __m128* bl1, __m128* bl2, + __m128* tr0, __m128* tr1, __m128* tr2, + __m128* br0, __m128* br1, __m128* br2) { + __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *tl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); + *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[4])); + *tr2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[5])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *bl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); + *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[4])); + *br2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[5])); +} +template +void VectorLoader::load1_4ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m128* tl0, __m128* tl1, __m128* tl2, + __m128* tl3, __m128* bl0, __m128* bl1, + __m128* bl2, __m128* bl3, __m128* tr0, + __m128* tr1, __m128* tr2, __m128* tr3, + __m128* br0, __m128* br1, __m128* br2, + __m128* br3) { + __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *tl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *tl3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); + *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[4])); + *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[5])); + *tr2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[6])); + *tr3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[7])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *bl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *bl3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); + *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[4])); + *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[5])); + *br2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[6])); + *br3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[7])); +} +template +void VectorLoader::load2_1ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m128* tl0, __m128* bl0, __m128* tr0, + __m128* br0) { + __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 1)); + *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 1)); + *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); +} +template +void VectorLoader::load2_2ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m128* tl0, __m128* tl1, __m128* bl0, + __m128* bl1, __m128* tr0, __m128* tr1, + __m128* br0, __m128* br1) { + __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 2)); + *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 2)); + *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); +} +template +void VectorLoader::load2_3ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m128* tl0, __m128* tl1, __m128* tl2, + __m128* bl0, __m128* bl1, __m128* bl2, + __m128* tr0, __m128* tr1, __m128* tr2, + __m128* br0, __m128* br1, __m128* br2) { + __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *tl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 3)); + *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *tr2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *bl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 3)); + *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *br2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); +} +template +void VectorLoader::load2_4ch(const T* lower_ptr, const T* upper_ptr, + int offset0, const __m128i* shuffle_masks, + __m128* tl0, __m128* tl1, __m128* tl2, + __m128* tl3, __m128* bl0, __m128* bl1, + __m128* bl2, __m128* bl3, __m128* tr0, + __m128* tr1, __m128* tr2, __m128* tr3, + __m128* br0, __m128* br1, __m128* br2, + __m128* br3) { + __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *tl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *tl3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); + raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 4)); + *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *tr2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *tr3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *bl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *bl3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); + raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 4)); + *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); + *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); + *br2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); + *br3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); +} +template +void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m128* tl0, __m128* bl0, + __m128* tr0, __m128* br0) { + __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + __m128i itr0 = extract_right_1ch(itl0); + __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibr0 = extract_right_1ch(ibl0); + __m128i itl1, itr1; + __m128i ibl1, ibr1; + if (offset1 == offset0) { + itl1 = itl0; + itr1 = itr0; + ibl1 = ibl0; + ibr1 = ibr0; + } else { + itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itr1 = extract_right_1ch(itl1); + ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibr1 = extract_right_1ch(ibl1); + } + __m128i itl2, itr2; + __m128i ibl2, ibr2; + if (offset2 == offset1) { + itl2 = itl1; + itr2 = itr1; + ibl2 = ibl1; + ibr2 = ibr1; + } else { + itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itr2 = extract_right_1ch(itl2); + ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibr2 = extract_right_1ch(ibl2); + } + __m128i itl3, itr3; + __m128i ibl3, ibr3; + if (offset3 == offset2) { + itl3 = itl2; + itr3 = itr2; + ibl3 = ibl2; + ibr3 = ibr2; + } else { + itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itr3 = extract_right_1ch(itl3); + ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibr3 = extract_right_1ch(ibl3); + } + pack_1ch(&itl0, &itl1, &itl2, &itl3); + *tl0 = to_fp32(itl0); + pack_1ch(&itr0, &itr1, &itr2, &itr3); + *tr0 = to_fp32(itr0); + pack_1ch(&ibl0, &ibl1, &ibl2, &ibl3); + *bl0 = to_fp32(ibl0); + pack_1ch(&ibr0, &ibr1, &ibr2, &ibr3); + *br0 = to_fp32(ibr0); +} +template +void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m128* tl0, __m128* tl1, + __m128* bl0, __m128* bl1, __m128* tr0, + __m128* tr1, __m128* br0, __m128* br1) { + __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + __m128i itr0 = extract_right_2ch(itl0); + __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibr0 = extract_right_2ch(ibl0); + __m128i itl1, itr1; + __m128i ibl1, ibr1; + if (offset1 == offset0) { + itl1 = itl0; + itr1 = itr0; + ibl1 = ibl0; + ibr1 = ibr0; + } else { + itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itr1 = extract_right_2ch(itl1); + ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibr1 = extract_right_2ch(ibl1); + } + __m128i itl2, itr2; + __m128i ibl2, ibr2; + if (offset2 == offset1) { + itl2 = itl1; + itr2 = itr1; + ibl2 = ibl1; + ibr2 = ibr1; + } else { + itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itr2 = extract_right_2ch(itl2); + ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibr2 = extract_right_2ch(ibl2); + } + __m128i itl3, itr3; + __m128i ibl3, ibr3; + if (offset3 == offset2) { + itl3 = itl2; + itr3 = itr2; + ibl3 = ibl2; + ibr3 = ibr2; + } else { + itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itr3 = extract_right_2ch(itl3); + ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibr3 = extract_right_2ch(ibl3); + } + pack_2ch(&itl0, &itl1, &itl2, &itl3); + *tl0 = to_fp32(itl0); + *tl1 = to_fp32(itl1); + pack_2ch(&itr0, &itr1, &itr2, &itr3); + *tr0 = to_fp32(itr0); + *tr1 = to_fp32(itr1); + pack_2ch(&ibl0, &ibl1, &ibl2, &ibl3); + *bl0 = to_fp32(ibl0); + *bl1 = to_fp32(ibl1); + pack_2ch(&ibr0, &ibr1, &ibr2, &ibr3); + *br0 = to_fp32(ibr0); + *br1 = to_fp32(ibr1); +} +template +void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m128* tl0, __m128* tl1, + __m128* tl2, __m128* bl0, __m128* bl1, + __m128* bl2, __m128* tr0, __m128* tr1, + __m128* tr2, __m128* br0, __m128* br1, + __m128* br2) { + __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + __m128i itr0 = extract_right_3ch(itl0); + __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibr0 = extract_right_3ch(ibl0); + __m128i itl1, itr1; + __m128i ibl1, ibr1; + if (offset1 == offset0) { + itl1 = itl0; + itr1 = itr0; + ibl1 = ibl0; + ibr1 = ibr0; + } else { + itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itr1 = extract_right_3ch(itl1); + ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibr1 = extract_right_3ch(ibl1); + } + __m128i itl2, itr2; + __m128i ibl2, ibr2; + if (offset2 == offset1) { + itl2 = itl1; + itr2 = itr1; + ibl2 = ibl1; + ibr2 = ibr1; + } else { + itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itr2 = extract_right_3ch(itl2); + ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibr2 = extract_right_3ch(ibl2); + } + __m128i itl3, itr3; + __m128i ibl3, ibr3; + if (offset3 == offset2) { + itl3 = itl2; + itr3 = itr2; + ibl3 = ibl2; + ibr3 = ibr2; + } else { + itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itr3 = extract_right_3ch(itl3); + ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibr3 = extract_right_3ch(ibl3); + } + pack_3ch(&itl0, &itl1, &itl2, &itl3); + *tl0 = to_fp32(itl0); + *tl1 = to_fp32(itl1); + *tl2 = to_fp32(itl2); + pack_3ch(&itr0, &itr1, &itr2, &itr3); + *tr0 = to_fp32(itr0); + *tr1 = to_fp32(itr1); + *tr2 = to_fp32(itr2); + pack_3ch(&ibl0, &ibl1, &ibl2, &ibl3); + *bl0 = to_fp32(ibl0); + *bl1 = to_fp32(ibl1); + *bl2 = to_fp32(ibl2); + pack_3ch(&ibr0, &ibr1, &ibr2, &ibr3); + *br0 = to_fp32(ibr0); + *br1 = to_fp32(ibr1); + *br2 = to_fp32(ibr2); +} +template +void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m128* tl0, __m128* tl1, + __m128* tl2, __m128* tl3, __m128* bl0, + __m128* bl1, __m128* bl2, __m128* bl3, + __m128* tr0, __m128* tr1, __m128* tr2, + __m128* tr3, __m128* br0, __m128* br1, + __m128* br2, __m128* br3) { + __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + __m128i itr0 = extract_right_4ch(itl0); + __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibr0 = extract_right_4ch(ibl0); + __m128i itl1, itr1; + __m128i ibl1, ibr1; + if (offset1 == offset0) { + itl1 = itl0; + itr1 = itr0; + ibl1 = ibl0; + ibr1 = ibr0; + } else { + itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itr1 = extract_right_4ch(itl1); + ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibr1 = extract_right_4ch(ibl1); + } + __m128i itl2, itr2; + __m128i ibl2, ibr2; + if (offset2 == offset1) { + itl2 = itl1; + itr2 = itr1; + ibl2 = ibl1; + ibr2 = ibr1; + } else { + itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itr2 = extract_right_4ch(itl2); + ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibr2 = extract_right_4ch(ibl2); + } + __m128i itl3, itr3; + __m128i ibl3, ibr3; + if (offset3 == offset2) { + itl3 = itl2; + itr3 = itr2; + ibl3 = ibl2; + ibr3 = ibr2; + } else { + itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itr3 = extract_right_4ch(itl3); + ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibr3 = extract_right_4ch(ibl3); + } + *tl0 = to_fp32(itl0); + *tl1 = to_fp32(itl1); + *tl2 = to_fp32(itl2); + *tl3 = to_fp32(itl3); + *tr0 = to_fp32(itr0); + *tr1 = to_fp32(itr1); + *tr2 = to_fp32(itr2); + *tr3 = to_fp32(itr3); + *bl0 = to_fp32(ibl0); + *bl1 = to_fp32(ibl1); + *bl2 = to_fp32(ibl2); + *bl3 = to_fp32(ibl3); + *br0 = to_fp32(ibr0); + *br1 = to_fp32(ibr1); + *br2 = to_fp32(ibr2); + *br3 = to_fp32(ibr3); +} +template +void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m128* tl0, __m128* bl0, + __m128* tr0, __m128* br0) { + __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + __m128i itr0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 1)); + __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibr0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 1)); + __m128i itl1, itr1; + __m128i ibl1, ibr1; + if (offset1 == offset0) { + itl1 = itl0; + itr1 = itr0; + ibl1 = ibl0; + ibr1 = ibr0; + } else { + itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itr1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 1)); + ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibr1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 1)); + } + __m128i itl2, itr2; + __m128i ibl2, ibr2; + if (offset2 == offset1) { + itl2 = itl1; + itr2 = itr1; + ibl2 = ibl1; + ibr2 = ibr1; + } else { + itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itr2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 1)); + ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibr2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 1)); + } + __m128i itl3, itr3; + __m128i ibl3, ibr3; + if (offset3 == offset2) { + itl3 = itl2; + itr3 = itr2; + ibl3 = ibl2; + ibr3 = ibr2; + } else { + itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itr3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 1)); + ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibr3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 1)); + } + pack_1ch(&itl0, &itl1, &itl2, &itl3); + *tl0 = to_fp32(itl0); + pack_1ch(&itr0, &itr1, &itr2, &itr3); + *tr0 = to_fp32(itr0); + pack_1ch(&ibl0, &ibl1, &ibl2, &ibl3); + *bl0 = to_fp32(ibl0); + pack_1ch(&ibr0, &ibr1, &ibr2, &ibr3); + *br0 = to_fp32(ibr0); +} +template +void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m128* tl0, __m128* tl1, + __m128* bl0, __m128* bl1, __m128* tr0, + __m128* tr1, __m128* br0, __m128* br1) { + __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + __m128i itr0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 2)); + __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibr0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 2)); + __m128i itl1, itr1; + __m128i ibl1, ibr1; + if (offset1 == offset0) { + itl1 = itl0; + itr1 = itr0; + ibl1 = ibl0; + ibr1 = ibr0; + } else { + itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itr1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 2)); + ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibr1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 2)); + } + __m128i itl2, itr2; + __m128i ibl2, ibr2; + if (offset2 == offset1) { + itl2 = itl1; + itr2 = itr1; + ibl2 = ibl1; + ibr2 = ibr1; + } else { + itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itr2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 2)); + ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibr2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 2)); + } + __m128i itl3, itr3; + __m128i ibl3, ibr3; + if (offset3 == offset2) { + itl3 = itl2; + itr3 = itr2; + ibl3 = ibl2; + ibr3 = ibr2; + } else { + itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itr3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 2)); + ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibr3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 2)); + } + pack_2ch(&itl0, &itl1, &itl2, &itl3); + *tl0 = to_fp32(itl0); + *tl1 = to_fp32(itl1); + pack_2ch(&itr0, &itr1, &itr2, &itr3); + *tr0 = to_fp32(itr0); + *tr1 = to_fp32(itr1); + pack_2ch(&ibl0, &ibl1, &ibl2, &ibl3); + *bl0 = to_fp32(ibl0); + *bl1 = to_fp32(ibl1); + pack_2ch(&ibr0, &ibr1, &ibr2, &ibr3); + *br0 = to_fp32(ibr0); + *br1 = to_fp32(ibr1); +} +template +void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m128* tl0, __m128* tl1, + __m128* tl2, __m128* bl0, __m128* bl1, + __m128* bl2, __m128* tr0, __m128* tr1, + __m128* tr2, __m128* br0, __m128* br1, + __m128* br2) { + __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + __m128i itr0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 3)); + __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibr0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 3)); + __m128i itl1, itr1; + __m128i ibl1, ibr1; + if (offset1 == offset0) { + itl1 = itl0; + itr1 = itr0; + ibl1 = ibl0; + ibr1 = ibr0; + } else { + itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itr1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 3)); + ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibr1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 3)); + } + __m128i itl2, itr2; + __m128i ibl2, ibr2; + if (offset2 == offset1) { + itl2 = itl1; + itr2 = itr1; + ibl2 = ibl1; + ibr2 = ibr1; + } else { + itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itr2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 3)); + ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibr2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 3)); + } + __m128i itl3, itr3; + __m128i ibl3, ibr3; + if (offset3 == offset2) { + itl3 = itl2; + itr3 = itr2; + ibl3 = ibl2; + ibr3 = ibr2; + } else { + itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itr3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 3)); + ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibr3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 3)); + } + pack_3ch(&itl0, &itl1, &itl2, &itl3); + *tl0 = to_fp32(itl0); + *tl1 = to_fp32(itl1); + *tl2 = to_fp32(itl2); + pack_3ch(&itr0, &itr1, &itr2, &itr3); + *tr0 = to_fp32(itr0); + *tr1 = to_fp32(itr1); + *tr2 = to_fp32(itr2); + pack_3ch(&ibl0, &ibl1, &ibl2, &ibl3); + *bl0 = to_fp32(ibl0); + *bl1 = to_fp32(ibl1); + *bl2 = to_fp32(ibl2); + pack_3ch(&ibr0, &ibr1, &ibr2, &ibr3); + *br0 = to_fp32(ibr0); + *br1 = to_fp32(ibr1); + *br2 = to_fp32(ibr2); +} +template +void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, + int offset0, int offset1, int offset2, + int offset3, __m128* tl0, __m128* tl1, + __m128* tl2, __m128* tl3, __m128* bl0, + __m128* bl1, __m128* bl2, __m128* bl3, + __m128* tr0, __m128* tr1, __m128* tr2, + __m128* tr3, __m128* br0, __m128* br1, + __m128* br2, __m128* br3) { + __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + __m128i itr0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 4)); + __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibr0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 4)); + __m128i itl1, itr1; + __m128i ibl1, ibr1; + if (offset1 == offset0) { + itl1 = itl0; + itr1 = itr0; + ibl1 = ibl0; + ibr1 = ibr0; + } else { + itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itr1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 4)); + ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibr1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 4)); + } + __m128i itl2, itr2; + __m128i ibl2, ibr2; + if (offset2 == offset1) { + itl2 = itl1; + itr2 = itr1; + ibl2 = ibl1; + ibr2 = ibr1; + } else { + itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itr2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 4)); + ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibr2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 4)); + } + __m128i itl3, itr3; + __m128i ibl3, ibr3; + if (offset3 == offset2) { + itl3 = itl2; + itr3 = itr2; + ibl3 = ibl2; + ibr3 = ibr2; + } else { + itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itr3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 4)); + ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibr3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 4)); + } + *tl0 = to_fp32(itl0); + *tl1 = to_fp32(itl1); + *tl2 = to_fp32(itl2); + *tl3 = to_fp32(itl3); + *tr0 = to_fp32(itr0); + *tr1 = to_fp32(itr1); + *tr2 = to_fp32(itr2); + *tr3 = to_fp32(itr3); + *bl0 = to_fp32(ibl0); + *bl1 = to_fp32(ibl1); + *bl2 = to_fp32(ibl2); + *bl3 = to_fp32(ibl3); + *br0 = to_fp32(ibr0); + *br1 = to_fp32(ibr1); + *br2 = to_fp32(ibr2); + *br3 = to_fp32(ibr3); +} +#endif + +// +// This class stores 4 pixels with n channels packed into n SSE vector words. +// Pixel values are converted to type U and packed before storage. +// Output type U must be one of uint8, int8, uint16, int16, int32, Eigen::half, +// bfloat16 or float. +// + +template +class VectorWriter { + public: + // convert 4 fp32 words to type U with. + // this function calls clip. + // resulting words are packed. + // U must be one of uint8, int8, uint16, int16, int32, Eigen::half, bfloat16 + // or float. + __m128i from_fp32(__m128 vec); + + // converts from fp32 to U by calling method from_fp32(...) + // writes 4 pixels with 1 channel to destination. + void write_1ch(U* destination, __m128* vec); + + // converts from fp32 to U by calling method from_fp32(...) + // writes 4 pixels with 1 channel to destination. + void write_2ch(U* destination, __m128* vec); + + // converts from fp32 to U by calling method from_fp32(...) + // writes 4 pixels with 1 channel to destination. + void write_3ch(U* destination, __m128* vec); + + // converts from fp32 to U by calling method from_fp32(...) + // writes 4 pixels with 1 channel to destination. + void write_4ch(U* destination, __m128* vec); + + private: + // clip 4 fp32 words to prevent overflow when converting to type U. + __m128 clip_(__m128 vec) { + // default is to do nothing, since the packing intrinsics include clipping. + return vec; + } + void write_1b_1ch(U* destination, __m128* vec) { + __m128i ivec = from_fp32(vec[0]); + _mm_store_ss((float*)(destination), _mm_castsi128_ps(ivec)); + } + void write_2b_1ch(U* destination, __m128* vec) { + __m128i ivec = from_fp32(vec[0]); + _mm_store_sd((double*)(destination), _mm_castsi128_pd(ivec)); + } + void write_4b_1ch(U* destination, __m128* vec) { + __m128i ivec = from_fp32(vec[0]); + _mm_storeu_si128((__m128i*)(destination), ivec); + } + void write_1b_2ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + __m128i mask = _mm_setr_epi32(-1, 0, 0, 0); + ivec1 = _mm_or_si128(_mm_and_si128(mask, ivec1), + _mm_slli_si128(_mm_and_si128(mask, ivec2), 4)); + _mm_store_sd((double*)(destination), _mm_castsi128_pd(ivec1)); + } + void write_2b_2ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + __m128i mask = _mm_setr_epi32(-1, -1, 0, 0); + ivec1 = _mm_or_si128(_mm_and_si128(mask, ivec1), + _mm_slli_si128(_mm_and_si128(mask, ivec2), 8)); + _mm_storeu_si128((__m128i*)(destination), ivec1); + } + void write_4b_2ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + _mm_storeu_si128((__m128i*)(destination), ivec1); + _mm_storeu_si128((__m128i*)(destination + 4), ivec2); + } + void write_1b_3ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + __m128i mask = _mm_setr_epi32(-1, 0, 0, 0); + ivec1 = _mm_or_si128(_mm_and_si128(mask, ivec1), + _mm_slli_si128(_mm_and_si128(mask, ivec2), 4)); + _mm_store_sd((double*)(destination), _mm_castsi128_pd(ivec1)); + __m128i ivec3 = from_fp32(vec[2]); + _mm_store_ss((float*)(destination + 8), _mm_castsi128_ps(ivec3)); + } + void write_2b_3ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + __m128i mask = _mm_setr_epi32(-1, -1, 0, 0); + ivec1 = _mm_or_si128(_mm_and_si128(mask, ivec1), + _mm_slli_si128(_mm_and_si128(mask, ivec2), 8)); + _mm_storeu_si128((__m128i*)(destination), ivec1); + __m128i ivec3 = from_fp32(vec[2]); + _mm_store_sd((double*)(destination + 8), _mm_castsi128_pd(ivec3)); + } + void write_4b_3ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + __m128i ivec3 = from_fp32(vec[2]); + _mm_storeu_si128((__m128i*)(destination), ivec1); + _mm_storeu_si128((__m128i*)(destination + 4), ivec2); + _mm_storeu_si128((__m128i*)(destination + 8), ivec3); + } + void write_1b_4ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + __m128i ivec3 = from_fp32(vec[2]); + __m128i ivec4 = from_fp32(vec[3]); + __m128i mask = _mm_setr_epi32(-1, 0, 0, 0); + __m128i ivec = _mm_and_si128(mask, ivec1); + ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec2), 4)); + ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec3), 8)); + ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec4), 12)); + _mm_storeu_si128((__m128i*)(destination), ivec); + } + void write_2b_4ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + __m128i ivec3 = from_fp32(vec[2]); + __m128i ivec4 = from_fp32(vec[3]); + __m128i mask = _mm_setr_epi32(-1, -1, 0, 0); + __m128i ivec = _mm_and_si128(mask, ivec1); + ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec2), 8)); + _mm_storeu_si128((__m128i*)(destination), ivec); + ivec = _mm_and_si128(mask, ivec3); + ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec4), 8)); + _mm_storeu_si128((__m128i*)(destination + 8), ivec); + } + void write_4b_4ch(U* destination, __m128* vec) { + __m128i ivec1 = from_fp32(vec[0]); + __m128i ivec2 = from_fp32(vec[1]); + __m128i ivec3 = from_fp32(vec[2]); + __m128i ivec4 = from_fp32(vec[3]); + _mm_storeu_si128((__m128i*)(destination), ivec1); + _mm_storeu_si128((__m128i*)(destination + 4), ivec2); + _mm_storeu_si128((__m128i*)(destination + 8), ivec3); + _mm_storeu_si128((__m128i*)(destination + 12), ivec4); + } +}; + +template <> +__m128 VectorWriter::clip_(__m128 vec) { + // clip against low limit, -2147483648. + // we round up to nearest number that can be represented as float. + __m128 lt_val = _mm_set1_ps(-2147483520.0f); + __m128 lt_mask = _mm_cmplt_ps(vec, lt_val); + vec = _mm_or_ps(_mm_andnot_ps(lt_mask, vec), _mm_and_ps(lt_mask, lt_val)); + // clip against hight limit, 2147483647. + // we round down to nearest number that can be represented as float. + __m128 gt_val = _mm_set1_ps(2147483520.0f); + __m128 gt_mask = _mm_cmpgt_ps(vec, gt_val); + vec = _mm_or_ps(_mm_andnot_ps(gt_mask, vec), _mm_and_ps(gt_mask, gt_val)); + return vec; +} +template <> +__m128 VectorWriter::clip_(__m128 vec) { + // clip against low limit, -65504.0f; + __m128 lt_val = _mm_set1_ps(-65504.0f); + __m128 lt_mask = _mm_cmplt_ps(vec, lt_val); + vec = _mm_or_ps(_mm_andnot_ps(lt_mask, vec), _mm_and_ps(lt_mask, lt_val)); + // clip against hight limit, 65504.0f. + __m128 gt_val = _mm_set1_ps(65504.0f); + __m128 gt_mask = _mm_cmpgt_ps(vec, gt_val); + vec = _mm_or_ps(_mm_andnot_ps(gt_mask, vec), _mm_and_ps(gt_mask, gt_val)); + return vec; +} + +template <> +__m128i VectorWriter::from_fp32(__m128 vec) { + __m128i ivec = _mm_cvttps_epi32(vec); + ivec = _mm_packs_epi32(ivec, ivec); + return _mm_packus_epi16(ivec, ivec); +} +template <> +__m128i VectorWriter::from_fp32(__m128 vec) { + __m128i ivec = _mm_cvttps_epi32(vec); + ivec = _mm_packs_epi32(ivec, ivec); + return _mm_packs_epi16(ivec, ivec); +} +template <> +__m128i VectorWriter::from_fp32(__m128 vec) { + __m128i ivec = _mm_cvttps_epi32(vec); + return _mm_packus_epi32(ivec, ivec); +} +template <> +__m128i VectorWriter::from_fp32(__m128 vec) { + __m128i ivec = _mm_cvttps_epi32(vec); + return _mm_packs_epi32(ivec, ivec); +} +template <> +__m128i VectorWriter::from_fp32(__m128 vec) { + return _mm_cvttps_epi32(clip_(vec)); +} +template <> +__m128i VectorWriter::from_fp32(__m128 vec) { +#ifdef __F16C__ + return _mm_cvtps_ph(vec, _MM_FROUND_TO_ZERO); +#else + // Emulation of _mm_cvtps_ph(vec, _MM_FROUND_TO_ZERO) intrinsic. + // + // fp16 :: 15=sign_bit, 14-10=exponent, 9-0=mantissa :: exp zero offset is 15 + // :: exponent of -15 (all 0) and +16 (all 1) are special numbers. + // fp32 :: 31=sign_bit, 30-23=exponent, 22-0=mantissa :: exp zero offset is + // 127 + // :: exponent of -127 (all 0) and +128 (all 1) are special numbers. + // + __m128i hw = _mm_castps_si128(vec); + // ..extract fp32 exponent and mantissa + __m128i fp16_sign_bit_msb = _mm_and_si128(_mm_set1_epi32(-2147483648), hw); + __m128i fp32_exponent_lsb = + _mm_and_si128(_mm_set1_epi32(255), _mm_srli_epi32(hw, 23)); + __m128i fp32_mantissa = _mm_and_si128(_mm_set1_epi32(8388607), hw); + // ..test for NaN + __m128i exponent_ones = + _mm_cmpeq_epi32(fp32_exponent_lsb, _mm_set1_epi32(255)); + __m128i mantissa_zero = _mm_cmpeq_epi32(fp32_mantissa, _mm_setzero_si128()); + __m128i infinity_mask = _mm_and_si128(mantissa_zero, exponent_ones); + // ..have to test for NaN on fp32 bits to avoid converting NaN to infinity + __m128i NaN_mask = _mm_andnot_si128(mantissa_zero, exponent_ones); + // ..compensate for exponent zero offset difference + __m128i fp16_exponent_lsb = + _mm_sub_epi32(fp32_exponent_lsb, _mm_set1_epi32(112)); + // ..clip output if fp16_exponent > 30 + __m128i saturated_mask = _mm_andnot_si128( + exponent_ones, _mm_cmpgt_epi32(fp16_exponent_lsb, _mm_set1_epi32(30))); + // ..generate subnormal number if fp16_exponent == 0 + // ..flush to zero if fp16_exponent < 0 + __m128i subnormal_mask = + _mm_cmpeq_epi32(fp16_exponent_lsb, _mm_setzero_si128()); + __m128i underflow_mask = + _mm_cmplt_epi32(fp16_exponent_lsb, _mm_setzero_si128()); + __m128i fp16_mantissa = _mm_srli_epi32(fp32_mantissa, 13); + // ..handle abnormal values + __m128i normal_number = + _mm_or_si128(_mm_slli_epi32(fp16_exponent_lsb, 10), fp16_mantissa); + __m128i subnormal_number = + _mm_or_si128(_mm_set1_epi32(512), _mm_srli_epi32(fp16_mantissa, 1)); + __m128i saturated_number = _mm_set1_epi32(31743); + __m128i infinity_number = _mm_set1_epi32(31744); + __m128i NaN_number = _mm_set1_epi32(32256); + __m128i number = _mm_andnot_si128(underflow_mask, normal_number); + number = _mm_or_si128(_mm_andnot_si128(subnormal_mask, number), + _mm_and_si128(subnormal_mask, subnormal_number)); + number = _mm_or_si128(_mm_andnot_si128(saturated_mask, number), + _mm_and_si128(saturated_mask, saturated_number)); + number = _mm_or_si128(_mm_andnot_si128(infinity_mask, number), + _mm_and_si128(infinity_mask, infinity_number)); + number = _mm_or_si128(_mm_andnot_si128(NaN_mask, number), + _mm_and_si128(NaN_mask, NaN_number)); + // ..or in sign bit + number = _mm_or_si128(fp16_sign_bit_msb, _mm_slli_epi32(number, 16)); + // ..move 16 bit words to lower portion of sse vector; + __m128i shuf_from_hi32 = _mm_setr_epi8(2, 3, 6, 7, 10, 11, 14, 15, -128, -128, + -128, -128, -128, -128, -128, -128); + number = _mm_shuffle_epi8(number, shuf_from_hi32); + return number; +#endif +} +template <> +__m128i VectorWriter::from_fp32(__m128 vec) { + // casting from float to bfloat16 simply means >> 16 + // we do this with a shuffle that also moves everything to lower portion of + // sse vector word + __m128i shuf_from_hi32 = _mm_setr_epi8(2, 3, 6, 7, 10, 11, 14, 15, -128, -128, + -128, -128, -128, -128, -128, -128); + return _mm_shuffle_epi8(_mm_castps_si128(vec), shuf_from_hi32); +} +template <> +__m128i VectorWriter::from_fp32(__m128 vec) { + // nothing to do in this case + return _mm_castps_si128(vec); +} + +template <> +void VectorWriter::write_1ch(uint8* destination, __m128* vec) { + write_1b_1ch(destination, vec); +} +template <> +void VectorWriter::write_1ch(int8* destination, __m128* vec) { + write_1b_1ch(destination, vec); +} +template <> +void VectorWriter::write_1ch(uint16* destination, __m128* vec) { + write_2b_1ch(destination, vec); +} +template <> +void VectorWriter::write_1ch(int16* destination, __m128* vec) { + write_2b_1ch(destination, vec); +} +template <> +void VectorWriter::write_1ch(int32* destination, __m128* vec) { + write_4b_1ch(destination, vec); +} +template <> +void VectorWriter::write_1ch(Eigen::half* destination, + __m128* vec) { + write_2b_1ch(destination, vec); +} +template <> +void VectorWriter::write_1ch(bfloat16* destination, __m128* vec) { + write_2b_1ch(destination, vec); +} +template <> +void VectorWriter::write_1ch(float* destination, __m128* vec) { + _mm_storeu_si128((__m128i*)(destination), _mm_castps_si128(vec[0])); +} + +template <> +void VectorWriter::write_2ch(uint8* destination, __m128* vec) { + write_1b_2ch(destination, vec); +} +template <> +void VectorWriter::write_2ch(int8* destination, __m128* vec) { + write_1b_2ch(destination, vec); +} +template <> +void VectorWriter::write_2ch(uint16* destination, __m128* vec) { + write_2b_2ch(destination, vec); +} +template <> +void VectorWriter::write_2ch(int16* destination, __m128* vec) { + write_2b_2ch(destination, vec); +} +template <> +void VectorWriter::write_2ch(int32* destination, __m128* vec) { + write_4b_2ch(destination, vec); +} +template <> +void VectorWriter::write_2ch(Eigen::half* destination, + __m128* vec) { + write_2b_2ch(destination, vec); +} +template <> +void VectorWriter::write_2ch(bfloat16* destination, __m128* vec) { + write_2b_2ch(destination, vec); +} +template <> +void VectorWriter::write_2ch(float* destination, __m128* vec) { + _mm_storeu_si128((__m128i*)(destination), _mm_castps_si128(vec[0])); + _mm_storeu_si128((__m128i*)(destination + 4), _mm_castps_si128(vec[1])); +} + +template <> +void VectorWriter::write_3ch(uint8* destination, __m128* vec) { + write_1b_3ch(destination, vec); +} +template <> +void VectorWriter::write_3ch(int8* destination, __m128* vec) { + write_1b_3ch(destination, vec); +} +template <> +void VectorWriter::write_3ch(uint16* destination, __m128* vec) { + write_2b_3ch(destination, vec); +} +template <> +void VectorWriter::write_3ch(int16* destination, __m128* vec) { + write_2b_3ch(destination, vec); +} +template <> +void VectorWriter::write_3ch(int32* destination, __m128* vec) { + write_4b_3ch(destination, vec); +} +template <> +void VectorWriter::write_3ch(Eigen::half* destination, + __m128* vec) { + write_2b_3ch(destination, vec); +} +template <> +void VectorWriter::write_3ch(bfloat16* destination, __m128* vec) { + write_2b_3ch(destination, vec); +} +template <> +void VectorWriter::write_3ch(float* destination, __m128* vec) { + _mm_storeu_si128((__m128i*)(destination), _mm_castps_si128(vec[0])); + _mm_storeu_si128((__m128i*)(destination + 4), _mm_castps_si128(vec[1])); + _mm_storeu_si128((__m128i*)(destination + 8), _mm_castps_si128(vec[2])); +} + +template <> +void VectorWriter::write_4ch(uint8* destination, __m128* vec) { + write_1b_4ch(destination, vec); +} +template <> +void VectorWriter::write_4ch(int8* destination, __m128* vec) { + write_1b_4ch(destination, vec); +} +template <> +void VectorWriter::write_4ch(uint16* destination, __m128* vec) { + write_2b_4ch(destination, vec); +} +template <> +void VectorWriter::write_4ch(int16* destination, __m128* vec) { + write_2b_4ch(destination, vec); +} +template <> +void VectorWriter::write_4ch(int32* destination, __m128* vec) { + write_4b_4ch(destination, vec); +} +template <> +void VectorWriter::write_4ch(Eigen::half* destination, + __m128* vec) { + write_2b_4ch(destination, vec); +} +template <> +void VectorWriter::write_4ch(bfloat16* destination, __m128* vec) { + write_2b_4ch(destination, vec); +} +template <> +void VectorWriter::write_4ch(float* destination, __m128* vec) { + _mm_storeu_si128((__m128i*)(destination), _mm_castps_si128(vec[0])); + _mm_storeu_si128((__m128i*)(destination + 4), _mm_castps_si128(vec[1])); + _mm_storeu_si128((__m128i*)(destination + 8), _mm_castps_si128(vec[2])); + _mm_storeu_si128((__m128i*)(destination + 12), _mm_castps_si128(vec[3])); +} + +template +class CropResizeCastImage : public VectorLoader, public VectorWriter { + public: + CropResizeCastImage(const int in_height, const int in_width, + const int out_height, const int out_width, + const int channels, const int min_ix, const int max_ix, + const CachedInterpolation* xs, const int min_iy, + const int max_iy, const CachedInterpolation* ys, + const float extrapolated_value, const bool flip_x, + const bool flip_y, const bool verbose = false, + const int allowed_load_groups = 15) + : verbose_(verbose), + allowed_load_groups_(allowed_load_groups), + in_height_(in_height), + in_width_(in_width), + out_height_(out_height), + out_width_(out_width), + channels_(channels), + min_ix_(min_ix), + max_ix_(max_ix), + min_iy_(min_iy), + max_iy_(max_iy), + ys_(ys), + extrapolated_value_(extrapolated_value), + flip_x_(flip_x), + flip_y_(flip_y), + in_row_size_(in_width * channels), + in_row_size_bytes_(in_width * channels * sizeof(T)), + out_row_size_(out_width * channels), + x0_(flip_x ? out_width - 1 - max_ix : min_ix), + x1_(flip_x ? out_width - 1 - min_ix : max_ix), + y0_(flip_y ? out_height - 1 - max_iy : min_iy), + y1_(flip_y ? out_height - 1 - min_iy : max_iy) { + // copy xs values, but filter out the following: + // xs[].lower == xs[].upper AND xs[].lerp == 0 + // xs[].lower == xs[].upper AND xs[].lerp == 1 + xs_ = new CachedInterpolation[max_ix_ - min_ix_ + 1]; + for (int i = min_ix_; i <= max_ix_; ++i) { + int ix = i - min_ix_; + int xs_lower = xs[ix].lower / channels_; + int xs_upper = xs[ix].upper / channels_; + if (xs_lower == xs_upper) { + if (xs[ix].lerp == 0.0f && xs_lower + 1 < in_width) { + // upper weight is zero + xs_upper = xs_lower + 1; + } else if (xs[ix].lerp == 1.0f && xs_upper - 1 >= 0) { + // lower weight is zero + xs_lower = xs_upper - 1; + } + } + xs_[ix].lower = xs_lower * channels_; + xs_[ix].upper = xs_upper * channels_; + xs_[ix].lerp = xs[ix].lerp; + } + _u_min_val = std::numeric_limits::min(); + _u_max_val = std::numeric_limits::max(); + _f_min_val = static_cast(_u_min_val); + _f_max_val = static_cast(_u_max_val); + Configure_(); + } + ~CropResizeCastImage() { + if (general_x_ != NULL) delete[] general_x_; + if (load1_x_ != NULL) delete[] load1_x_; + if (load2_x_ != NULL) delete[] load2_x_; + if (load4_x_ != NULL) delete[] load4_x_; + if (load8_x_ != NULL) delete[] load8_x_; + if (load1_offsets_ != NULL) delete[] load1_offsets_; + if (load2_offsets_ != NULL) delete[] load2_offsets_; + if (load4_offsets_ != NULL) delete[] load4_offsets_; + if (load8_offsets_ != NULL) delete[] load8_offsets_; + if (load1_shuffle_masks_ != NULL) delete[] load1_shuffle_masks_; + if (load2_shuffle_masks_ != NULL) delete[] load2_shuffle_masks_; + if (load1_mmxs_lerp_ != NULL) delete[] load1_mmxs_lerp_; + if (load2_mmxs_lerp_ != NULL) delete[] load2_mmxs_lerp_; + if (load4_mmxs_lerp_ != NULL) delete[] load4_mmxs_lerp_; + if (load8_mmxs_lerp_ != NULL) delete[] load8_mmxs_lerp_; + delete[] xs_; + } + + private: + // constructor arguments + const bool verbose_; + // this value is meant for unit testing. + // set this to 15 for normal execution. + // its an OR of flags for the different load group. + // 1 -> load4from1 + // 2 -> load4from2 + // 4 -> load4from4 + // 8 -> load4from8 + const int allowed_load_groups_; + const int in_height_, in_width_, out_height_, out_width_; + const int channels_; + const int min_ix_, max_ix_, min_iy_, max_iy_; + const CachedInterpolation* ys_; + CachedInterpolation* xs_; + const float extrapolated_value_; + const bool flip_x_, flip_y_; + // computed arguments + const int in_row_size_; + const int in_row_size_bytes_; + const int out_row_size_; + const int x0_, x1_; + const int y0_, y1_; + + // helper methods + void ResizeRow_load1_1ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load2_1ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load4_1ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load8_1ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load1_2ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load2_2ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load4_2ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load8_2ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load1_3ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load2_3ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load4_3ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load8_3ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load1_4ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load2_4ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load4_4ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load8_4ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_general_(const float ys_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr); + + // configuration parameters + int num_general_, num_load1_, num_load2_, num_load4_, num_load8_; + int *load1_offsets_, *load2_offsets_, *load4_offsets_, *load8_offsets_; + int *general_x_, *load1_x_, *load2_x_, *load4_x_, *load8_x_; + __m128i *load1_shuffle_masks_, *load2_shuffle_masks_; + __m128 *load1_mmxs_lerp_, *load2_mmxs_lerp_, *load4_mmxs_lerp_, + *load8_mmxs_lerp_; + float _f_min_val, _f_max_val; + U _u_min_val, _u_max_val; + // configuration methods + void Configure_(); + int DetermineLoadGroup_(const int x); + bool ComputeXIndexRange_(const int x, int* min_xidx, int* max_xidx); + bool Load1_ok_( + const int min_xidx, + const int max_xidx); // xs - pointer to first xs for this load group + bool Load2_ok_( + const int min_xidx, + const int max_xidx); // xs - pointer to first xs for this load group + bool Load4_ok_(const int min_xidx, const int max_xidx); + bool Load8_ok_(const int min_xidx, const int max_xidx); + + // debugging + int y_; + const T* input_image_; + U* output_image_; + + public: + // + // public client methods + // + + // convenience function that determines if clipping is necessary + // in order to prevent overflow when casting to the output type U. + static bool clip_necessary(); + + // resize image + void Resize(const T* input_image, U* output_image); +}; + +template +void CropResizeCastImage::Resize(const T* input_image, U* output_image) { + // store these for debugging + input_image_ = input_image; + output_image_ = output_image_; + // + U uEx = cast_to(extrapolated_value_, _f_min_val, _f_max_val, _u_min_val, + _u_max_val); + // extrapolate top + if (min_iy_ > 0) { + U* p = flip_y_ ? output_image + out_row_size_ * (out_height_ - min_iy_) + : output_image; + int nn = out_row_size_ * min_iy_; + for (int i = 0; i < nn; ++i) p[i] = uEx; + } + // extrapolate bottom + if (max_iy_ < out_height_ - 1) { + U* p = + flip_y_ ? output_image : output_image + out_row_size_ * (max_iy_ + 1); + int nn = out_row_size_ * (out_height_ - 1 - max_iy_); + for (int i = 0; i < nn; ++i) p[i] = uEx; + } + // extrapolate left + if (min_ix_ > 0) { + for (int iy = min_iy_; iy <= max_iy_; ++iy) { + int xx0 = flip_x_ ? (out_width_ - min_ix_) * channels_ : 0; + int nxx = min_ix_ * channels_; + U* p = output_image + xx0 + + out_row_size_ * (flip_y_ ? out_height_ - 1 - iy : iy); + for (int ix = 0; ix < nxx; ++ix) { + p[ix] = uEx; + } + } + } + // extrapolate right + if (max_ix_ < out_width_ - 1) { + for (int iy = min_iy_; iy <= max_iy_; ++iy) { + int xx0 = flip_x_ ? 0 : (max_ix_ + 1) * channels_; + int nxx = (out_width_ - 1 - max_ix_) * channels_; + U* p = output_image + xx0 + + out_row_size_ * (flip_y_ ? out_height_ - 1 - iy : iy); + for (int ix = 0; ix < nxx; ++ix) { + p[ix] = uEx; + } + } + } + // interpolation region + int y = y0_; + for (y = y0_; y + 1 <= y1_; y += 2) { + y_ = y; + const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; + const float yA_lerp = ys_[iyA].lerp; + const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); + const T* ysA_input_lower_ptr = + input_image + ys_[iyA].lower * in_width_ * channels_; + const T* ysA_input_upper_ptr = + input_image + ys_[iyA].upper * in_width_ * channels_; + U* ysA_output_ptr = output_image + y * out_width_ * channels_; + const int iyB = + flip_y_ ? out_height_ - 1 - min_iy_ - (y + 1) : (y + 1) - min_iy_; + const float yB_lerp = ys_[iyB].lerp; + const __m128 ysB_lerp = _mm_set1_ps(yB_lerp); + const T* ysB_input_lower_ptr = + input_image + ys_[iyB].lower * in_width_ * channels_; + const T* ysB_input_upper_ptr = + input_image + ys_[iyB].upper * in_width_ * channels_; + U* ysB_output_ptr = output_image + (y + 1) * out_width_ * channels_; + if (channels_ == 1) { + this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + } else if (channels_ == 2) { + this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + } else if (channels_ == 3) { + this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + } else if (channels_ == 4) { + this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + } else { + assert(false); + } + // printf("*2 :: y=%d, channels_=%d, + // num_load8_=%d\n",y,channels_,num_load8_); + } + for (; y <= y1_; ++y) { + y_ = y; + const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; + const float yA_lerp = ys_[iyA].lerp; + const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); + const T* ysA_input_lower_ptr = + input_image + ys_[iyA].lower * in_width_ * channels_; + const T* ysA_input_upper_ptr = + input_image + ys_[iyA].upper * in_width_ * channels_; + U* ysA_output_ptr = output_image + y * out_width_ * channels_; + if (channels_ == 1) { + this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + } else if (channels_ == 2) { + this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + } else if (channels_ == 3) { + this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + } else if (channels_ == 4) { + this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + } else { + assert(false); + } + // printf("*1 :: y=%d\n",y); + } +} + +template +void CropResizeCastImage::ResizeRow_general_(const float ys_lerp, + const T* ys_input_lower_ptr, + const T* ys_input_upper_ptr, + U* output_y_ptr) { + for (int current = 0; current < num_general_; ++current) { + int x = general_x_[current]; + const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - x : x - min_ix_; + const int xs_lower = xs_[ix].lower; + const int xs_upper = xs_[ix].upper; + const float xs_lerp = xs_[ix].lerp; + for (int ichan = 0; ichan < channels_; ++ichan) { + const float top_left0(ys_input_lower_ptr[xs_lower + ichan]); + const float top_right0(ys_input_lower_ptr[xs_upper + ichan]); + const float bottom_left0(ys_input_upper_ptr[xs_lower + ichan]); + const float bottom_right0(ys_input_upper_ptr[xs_upper + ichan]); + float result0 = compute_lerp(top_left0, top_right0, bottom_left0, + bottom_right0, xs_lerp, ys_lerp); + output_y_ptr[x * channels_ + ichan] = + cast_to(result0, _f_min_val, _f_max_val, _u_min_val, _u_max_val); + } + } +} + +#define CHANNELS 1 +// Resize all points that fall in the 'load4from1' group for an entire row of a +// 1 channel image. +template +void CropResizeCastImage::ResizeRow_load1_1ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load1_; ++current) { + __m128* mmxs_lerp = + (__m128*)(load1_shuffle_masks_ + current * CHANNELS * 3); + __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; +#ifdef __AVX2__ + __m256 left0, right0; + this->load1_1ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load1_offsets_[current], shuffle_masks, &left0, &right0); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); +#else + __m128 tl0, bl0, tr0, br0; + this->load1_1ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load1_offsets_[current], shuffle_masks, &tl0, &bl0, &tr0, + &br0); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); +#endif + __m128 res[1]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + this->write_1ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from2' group for an entire row of a +// 1 channel image. +template +void CropResizeCastImage::ResizeRow_load2_1ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load2_; ++current) { + __m128* mmxs_lerp = + (__m128*)(load2_shuffle_masks_ + current * CHANNELS * 2); + __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; +#ifdef __AVX2__ + __m256 left0, right0; + this->load2_1ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load2_offsets_[current], shuffle_masks, &left0, &right0); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); +#else + __m128 tl0, bl0, tr0, br0; + this->load2_1ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load2_offsets_[current], shuffle_masks, &tl0, &bl0, &tr0, + &br0); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); +#endif + __m128 res[1]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + this->write_1ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from4' group for an entire row of a +// 1 channel image. +template +void CropResizeCastImage::ResizeRow_load4_1ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load4_; ++current) { + __m128* mmxs_lerp = (__m128*)(load4_mmxs_lerp_ + current * CHANNELS); +#ifdef __AVX2__ + __m256 left0, right0; + this->load4_1ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load4_offsets_[current * 4], + load4_offsets_[current * 4 + 1], load4_offsets_[current * 4 + 2], + load4_offsets_[current * 4 + 3], &left0, &right0); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); +#else + __m128 tl0, bl0, tr0, br0; + this->load4_1ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load4_offsets_[current * 4], + load4_offsets_[current * 4 + 1], load4_offsets_[current * 4 + 2], + load4_offsets_[current * 4 + 3], &tl0, &bl0, &tr0, &br0); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); +#endif + __m128 res[1]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + this->write_1ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from8' group for an entire row of a +// 1 channel image. +template +void CropResizeCastImage::ResizeRow_load8_1ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load8_; ++current) { + __m128* mmxs_lerp = (__m128*)(load8_mmxs_lerp_ + current * CHANNELS); +#ifdef __AVX2__ + __m256 left0, right0; + this->load8_1ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load8_offsets_[current * 4], + load8_offsets_[current * 4 + 1], load8_offsets_[current * 4 + 2], + load8_offsets_[current * 4 + 3], &left0, &right0); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); +#else + __m128 tl0, bl0, tr0, br0; + this->load8_1ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load8_offsets_[current * 4], + load8_offsets_[current * 4 + 1], load8_offsets_[current * 4 + 2], + load8_offsets_[current * 4 + 3], &tl0, &bl0, &tr0, &br0); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); +#endif + __m128 res[1]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + this->write_1ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); + } +} +#undef CHANNELS + +#define CHANNELS 2 +// Resize all points that fall in the 'load4from1' group for an entire row of a +// 2 channel image. +template +void CropResizeCastImage::ResizeRow_load1_2ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load1_; ++current) { + __m128* mmxs_lerp = + (__m128*)(load1_shuffle_masks_ + current * CHANNELS * 3); + __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; +#ifdef __AVX2__ + __m256 left0, left1, right0, right1; + this->load1_2ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load1_offsets_[current], shuffle_masks, &left0, &left1, + &right0, &right1); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); +#else + __m128 tl0, tl1, bl0, bl1, tr0, tr1, br0, br1; + this->load1_2ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load1_offsets_[current], shuffle_masks, &tl0, &tl1, &bl0, + &bl1, &tr0, &tr1, &br0, &br1); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); +#endif + __m128 res[2]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + this->write_2ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from2' group for an entire row of a +// 2 channel image. +template +void CropResizeCastImage::ResizeRow_load2_2ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load2_; ++current) { + __m128* mmxs_lerp = + (__m128*)(load2_shuffle_masks_ + current * CHANNELS * 2); + __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; +#ifdef __AVX2__ + __m256 left0, left1, right0, right1; + this->load2_2ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load2_offsets_[current], shuffle_masks, &left0, &left1, + &right0, &right1); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); +#else + __m128 tl0, tl1, bl0, bl1, tr0, tr1, br0, br1; + this->load2_2ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load2_offsets_[current], shuffle_masks, &tl0, &tl1, &bl0, + &bl1, &tr0, &tr1, &br0, &br1); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); +#endif + __m128 res[2]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + this->write_2ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from4' group for an entire row of a +// 2 channel image. +template +void CropResizeCastImage::ResizeRow_load4_2ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load4_; ++current) { + __m128* mmxs_lerp = (__m128*)(load4_mmxs_lerp_ + current * CHANNELS); +#ifdef __AVX2__ + __m256 left0, left1, right0, right1; + this->load4_2ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load4_offsets_[current * 4], + load4_offsets_[current * 4 + 1], load4_offsets_[current * 4 + 2], + load4_offsets_[current * 4 + 3], &left0, &left1, &right0, &right1); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); +#else + __m128 tl0, tl1, bl0, bl1, tr0, tr1, br0, br1; + this->load4_2ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load4_offsets_[current * 4], + load4_offsets_[current * 4 + 1], load4_offsets_[current * 4 + 2], + load4_offsets_[current * 4 + 3], &tl0, &tl1, &bl0, &bl1, &tr0, &tr1, + &br0, &br1); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); +#endif + __m128 res[2]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + this->write_2ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from8' group for an entire row of a +// 2 channel image. +template +void CropResizeCastImage::ResizeRow_load8_2ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load8_; ++current) { + __m128* mmxs_lerp = (__m128*)(load8_mmxs_lerp_ + current * CHANNELS); +#ifdef __AVX2__ + __m256 left0, left1, right0, right1; + this->load8_2ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load8_offsets_[current * 4], + load8_offsets_[current * 4 + 1], load8_offsets_[current * 4 + 2], + load8_offsets_[current * 4 + 3], &left0, &left1, &right0, &right1); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); +#else + __m128 tl0, tl1, bl0, bl1, tr0, tr1, br0, br1; + this->load8_2ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load8_offsets_[current * 4], + load8_offsets_[current * 4 + 1], load8_offsets_[current * 4 + 2], + load8_offsets_[current * 4 + 3], &tl0, &tl1, &bl0, &bl1, &tr0, &tr1, + &br0, &br1); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); +#endif + __m128 res[2]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + this->write_2ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); + } +} +#undef CHANNELS + +#define CHANNELS 3 +// Resize all points that fall in the 'load4from1' group for an entire row of a +// 3 channel image. +template +void CropResizeCastImage::ResizeRow_load1_3ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load1_; ++current) { + __m128* mmxs_lerp = + (__m128*)(load1_shuffle_masks_ + current * CHANNELS * 3); + __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; +#ifdef __AVX2__ + __m256 left0, left1, left2, right0, right1, right2; + this->load1_3ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load1_offsets_[current], shuffle_masks, &left0, &left1, + &left2, &right0, &right1, &right2); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[2]))); + __m256 hori2 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right2, left2), left2); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); + __m128 top2 = _mm256_castps256_ps128(hori2); + __m128 bot2 = _mm256_extractf128_ps(hori2, 1); +#else + __m128 tl0, tl1, tl2, bl0, bl1, bl2, tr0, tr1, tr2, br0, br1, br2; + this->load1_3ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load1_offsets_[current], shuffle_masks, &tl0, &tl1, &tl2, + &bl0, &bl1, &bl2, &tr0, &tr1, &tr2, &br0, &br1, &br2); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); + x_lerp = mmxs_lerp[2]; + __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); + __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); +#endif + __m128 res[3]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); + this->write_3ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from2' group for an entire row of a +// 3 channel image. +template +void CropResizeCastImage::ResizeRow_load2_3ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load2_; ++current) { + __m128* mmxs_lerp = + (__m128*)(load2_shuffle_masks_ + current * CHANNELS * 2); + __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; +#ifdef __AVX2__ + __m256 left0, left1, left2, right0, right1, right2; + this->load2_3ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load2_offsets_[current], shuffle_masks, &left0, &left1, + &left2, &right0, &right1, &right2); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[2]))); + __m256 hori2 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right2, left2), left2); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); + __m128 top2 = _mm256_castps256_ps128(hori2); + __m128 bot2 = _mm256_extractf128_ps(hori2, 1); +#else + __m128 tl0, tl1, tl2, bl0, bl1, bl2, tr0, tr1, tr2, br0, br1, br2; + this->load2_3ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load2_offsets_[current], shuffle_masks, &tl0, &tl1, &tl2, + &bl0, &bl1, &bl2, &tr0, &tr1, &tr2, &br0, &br1, &br2); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); + x_lerp = mmxs_lerp[2]; + __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); + __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); +#endif + __m128 res[3]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); + this->write_3ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from4' group for an entire row of a +// 3 channel image. +template +void CropResizeCastImage::ResizeRow_load4_3ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load4_; ++current) { + __m128* mmxs_lerp = (__m128*)(load4_mmxs_lerp_ + current * CHANNELS); +#ifdef __AVX2__ + __m256 left0, left1, left2, right0, right1, right2; + this->load4_3ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load4_offsets_[current * 4], + load4_offsets_[current * 4 + 1], load4_offsets_[current * 4 + 2], + load4_offsets_[current * 4 + 3], &left0, &left1, &left2, &right0, + &right1, &right2); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[2]))); + __m256 hori2 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right2, left2), left2); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); + __m128 top2 = _mm256_castps256_ps128(hori2); + __m128 bot2 = _mm256_extractf128_ps(hori2, 1); +#else + __m128 tl0, tl1, tl2, bl0, bl1, bl2, tr0, tr1, tr2, br0, br1, br2; + this->load4_3ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load4_offsets_[current * 4], + load4_offsets_[current * 4 + 1], load4_offsets_[current * 4 + 2], + load4_offsets_[current * 4 + 3], &tl0, &tl1, &tl2, &bl0, &bl1, &bl2, + &tr0, &tr1, &tr2, &br0, &br1, &br2); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); + x_lerp = mmxs_lerp[2]; + __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); + __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); +#endif + __m128 res[3]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); + this->write_3ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from8' group for an entire row of a +// 3 channel image. +template +void CropResizeCastImage::ResizeRow_load8_3ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load8_; ++current) { + __m128* mmxs_lerp = (__m128*)(load8_mmxs_lerp_ + current * CHANNELS); +#ifdef __AVX2__ + __m256 left0, left1, left2, right0, right1, right2; + this->load8_3ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load8_offsets_[current * 4], + load8_offsets_[current * 4 + 1], load8_offsets_[current * 4 + 2], + load8_offsets_[current * 4 + 3], &left0, &left1, &left2, &right0, + &right1, &right2); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[2]))); + __m256 hori2 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right2, left2), left2); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); + __m128 top2 = _mm256_castps256_ps128(hori2); + __m128 bot2 = _mm256_extractf128_ps(hori2, 1); +#else + __m128 tl0, tl1, tl2, bl0, bl1, bl2, tr0, tr1, tr2, br0, br1, br2; + this->load8_3ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load8_offsets_[current * 4], + load8_offsets_[current * 4 + 1], load8_offsets_[current * 4 + 2], + load8_offsets_[current * 4 + 3], &tl0, &tl1, &tl2, &bl0, &bl1, &bl2, + &tr0, &tr1, &tr2, &br0, &br1, &br2); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); + x_lerp = mmxs_lerp[2]; + __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); + __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); +#endif + __m128 res[3]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); + this->write_3ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); + } +} +#undef CHANNELS + +#define CHANNELS 4 +// Resize all points that fall in the 'load4from1' group for an entire row of a +// 4 channel image. +template +void CropResizeCastImage::ResizeRow_load1_4ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load1_; ++current) { + __m128* mmxs_lerp = + (__m128*)(load1_shuffle_masks_ + current * CHANNELS * 3); + __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; +#ifdef __AVX2__ + __m256 left0, left1, left2, left3, right0, right1, right2, right3; + this->load1_4ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load1_offsets_[current], shuffle_masks, &left0, &left1, + &left2, &left3, &right0, &right1, &right2, &right3); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[2]))); + __m256 hori2 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right2, left2), left2); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[3]))); + __m256 hori3 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right3, left3), left3); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); + __m128 top2 = _mm256_castps256_ps128(hori2); + __m128 bot2 = _mm256_extractf128_ps(hori2, 1); + __m128 top3 = _mm256_castps256_ps128(hori3); + __m128 bot3 = _mm256_extractf128_ps(hori3, 1); +#else + __m128 tl0, tl1, tl2, tl3, bl0, bl1, bl2, bl3, tr0, tr1, tr2, tr3, br0, br1, + br2, br3; + this->load1_4ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load1_offsets_[current], shuffle_masks, &tl0, &tl1, &tl2, + &tl3, &bl0, &bl1, &bl2, &bl3, &tr0, &tr1, &tr2, &tr3, &br0, + &br1, &br2, &br3); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); + x_lerp = mmxs_lerp[2]; + __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); + __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); + x_lerp = mmxs_lerp[3]; + __m128 top3 = _mm_add_ps(tl3, _mm_mul_ps(x_lerp, _mm_sub_ps(tr3, tl3))); + __m128 bot3 = _mm_add_ps(bl3, _mm_mul_ps(x_lerp, _mm_sub_ps(br3, bl3))); +#endif + __m128 res[4]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); + res[3] = _mm_add_ps(top3, _mm_mul_ps(y_lerp, _mm_sub_ps(bot3, top3))); + this->write_4ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from2' group for an entire row of a +// 4 channel image. +template +void CropResizeCastImage::ResizeRow_load2_4ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load2_; ++current) { + __m128* mmxs_lerp = + (__m128*)(load2_shuffle_masks_ + current * CHANNELS * 2); + __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; +#ifdef __AVX2__ + __m256 left0, left1, left2, left3, right0, right1, right2, right3; + this->load2_4ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load2_offsets_[current], shuffle_masks, &left0, &left1, + &left2, &left3, &right0, &right1, &right2, &right3); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[2]))); + __m256 hori2 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right2, left2), left2); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[3]))); + __m256 hori3 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right3, left3), left3); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); + __m128 top2 = _mm256_castps256_ps128(hori2); + __m128 bot2 = _mm256_extractf128_ps(hori2, 1); + __m128 top3 = _mm256_castps256_ps128(hori3); + __m128 bot3 = _mm256_extractf128_ps(hori3, 1); +#else + __m128 tl0, tl1, tl2, tl3, bl0, bl1, bl2, bl3, tr0, tr1, tr2, tr3, br0, br1, + br2, br3; + this->load2_4ch(ysA_input_lower_ptr, ysA_input_upper_ptr, + load2_offsets_[current], shuffle_masks, &tl0, &tl1, &tl2, + &tl3, &bl0, &bl1, &bl2, &bl3, &tr0, &tr1, &tr2, &tr3, &br0, + &br1, &br2, &br3); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); + x_lerp = mmxs_lerp[2]; + __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); + __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); + x_lerp = mmxs_lerp[3]; + __m128 top3 = _mm_add_ps(tl3, _mm_mul_ps(x_lerp, _mm_sub_ps(tr3, tl3))); + __m128 bot3 = _mm_add_ps(bl3, _mm_mul_ps(x_lerp, _mm_sub_ps(br3, bl3))); +#endif + __m128 res[4]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); + res[3] = _mm_add_ps(top3, _mm_mul_ps(y_lerp, _mm_sub_ps(bot3, top3))); + this->write_4ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from4' group for an entire row of a +// 4 channel image. +template +void CropResizeCastImage::ResizeRow_load4_4ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load4_; ++current) { + __m128* mmxs_lerp = (__m128*)(load4_mmxs_lerp_ + current * CHANNELS); +#ifdef __AVX2__ + __m256 left0, left1, left2, left3, right0, right1, right2, right3; + this->load4_4ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load4_offsets_[current * 4], + load4_offsets_[current * 4 + 1], load4_offsets_[current * 4 + 2], + load4_offsets_[current * 4 + 3], &left0, &left1, &left2, &left3, + &right0, &right1, &right2, &right3); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[2]))); + __m256 hori2 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right2, left2), left2); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[3]))); + __m256 hori3 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right3, left3), left3); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); + __m128 top2 = _mm256_castps256_ps128(hori2); + __m128 bot2 = _mm256_extractf128_ps(hori2, 1); + __m128 top3 = _mm256_castps256_ps128(hori3); + __m128 bot3 = _mm256_extractf128_ps(hori3, 1); +#else + __m128 tl0, tl1, tl2, tl3, bl0, bl1, bl2, bl3, tr0, tr1, tr2, tr3, br0, br1, + br2, br3; + this->load4_4ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load4_offsets_[current * 4], + load4_offsets_[current * 4 + 1], load4_offsets_[current * 4 + 2], + load4_offsets_[current * 4 + 3], &tl0, &tl1, &tl2, &tl3, &bl0, &bl1, + &bl2, &bl3, &tr0, &tr1, &tr2, &tr3, &br0, &br1, &br2, &br3); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); + x_lerp = mmxs_lerp[2]; + __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); + __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); + x_lerp = mmxs_lerp[3]; + __m128 top3 = _mm_add_ps(tl3, _mm_mul_ps(x_lerp, _mm_sub_ps(tr3, tl3))); + __m128 bot3 = _mm_add_ps(bl3, _mm_mul_ps(x_lerp, _mm_sub_ps(br3, bl3))); +#endif + __m128 res[4]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); + res[3] = _mm_add_ps(top3, _mm_mul_ps(y_lerp, _mm_sub_ps(bot3, top3))); + this->write_4ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); + } +} +// Resize all points that fall in the 'load4from8' group for an entire row of a +// 4 channel image. +template +void CropResizeCastImage::ResizeRow_load8_4ch_( + const __m128 y_lerp, const T* ysA_input_lower_ptr, + const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + for (int current = 0; current < num_load8_; ++current) { + __m128* mmxs_lerp = (__m128*)(load8_mmxs_lerp_ + current * CHANNELS); +#ifdef __AVX2__ + __m256 left0, left1, left2, left3, right0, right1, right2, right3; + this->load8_4ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load8_offsets_[current * 4], + load8_offsets_[current * 4 + 1], load8_offsets_[current * 4 + 2], + load8_offsets_[current * 4 + 3], &left0, &left1, &left2, &left3, + &right0, &right1, &right2, &right3); + + __m256 x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[0]))); + __m256 hori0 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right0, left0), left0); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[1]))); + __m256 hori1 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right1, left1), left1); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[2]))); + __m256 hori2 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right2, left2), left2); + x_lerp = _mm256_castsi256_ps( + _mm256_broadcastsi128_si256(_mm_castps_si128(mmxs_lerp[3]))); + __m256 hori3 = _mm256_fmadd_ps(x_lerp, _mm256_sub_ps(right3, left3), left3); + + __m128 top0 = _mm256_castps256_ps128(hori0); + __m128 bot0 = _mm256_extractf128_ps(hori0, 1); + __m128 top1 = _mm256_castps256_ps128(hori1); + __m128 bot1 = _mm256_extractf128_ps(hori1, 1); + __m128 top2 = _mm256_castps256_ps128(hori2); + __m128 bot2 = _mm256_extractf128_ps(hori2, 1); + __m128 top3 = _mm256_castps256_ps128(hori3); + __m128 bot3 = _mm256_extractf128_ps(hori3, 1); +#else + __m128 tl0, tl1, tl2, tl3, bl0, bl1, bl2, bl3, tr0, tr1, tr2, tr3, br0, br1, + br2, br3; + this->load8_4ch( + ysA_input_lower_ptr, ysA_input_upper_ptr, load8_offsets_[current * 4], + load8_offsets_[current * 4 + 1], load8_offsets_[current * 4 + 2], + load8_offsets_[current * 4 + 3], &tl0, &tl1, &tl2, &tl3, &bl0, &bl1, + &bl2, &bl3, &tr0, &tr1, &tr2, &tr3, &br0, &br1, &br2, &br3); + + __m128 x_lerp = mmxs_lerp[0]; + __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); + __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); + x_lerp = mmxs_lerp[1]; + __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); + __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); + x_lerp = mmxs_lerp[2]; + __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); + __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); + x_lerp = mmxs_lerp[3]; + __m128 top3 = _mm_add_ps(tl3, _mm_mul_ps(x_lerp, _mm_sub_ps(tr3, tl3))); + __m128 bot3 = _mm_add_ps(bl3, _mm_mul_ps(x_lerp, _mm_sub_ps(br3, bl3))); +#endif + __m128 res[4]; + res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); + res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); + res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); + res[3] = _mm_add_ps(top3, _mm_mul_ps(y_lerp, _mm_sub_ps(bot3, top3))); + this->write_4ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); + } +} +#undef CHANNELS + +template +void CropResizeCastImage::Configure_() { + // num_cases[0] = general case + // num_cases[1] = load4from1 + // num_cases[2] = load4from2 + // num_cases[3] = load4from4 + // num_cases[4] = load4from8 + int num_cases[5]; + for (int i = 0; i < 5; ++i) num_cases[i] = 0; + for (int x = x0_; x <= x1_; ++x) { + int load_group = this->DetermineLoadGroup_(x); + assert(load_group >= 0 && load_group <= 4); + ++num_cases[load_group]; + // load_group == 0 -> general case, pixel by pixel + // every other value indidcates 1+3 = 4 pixels were processed this iteration + if (load_group > 0) x += 3; + } + num_general_ = num_cases[0]; + num_load1_ = num_cases[1]; + num_load2_ = num_cases[2]; + num_load4_ = num_cases[3]; + num_load8_ = num_cases[4]; + if (num_general_ > 0) { + general_x_ = new int[num_general_]; + } else { + general_x_ = NULL; + } + if (num_load1_ > 0) { + load1_offsets_ = new int[num_load1_]; + load1_shuffle_masks_ = new __m128i[num_load1_ * channels_ * 3]; + load1_mmxs_lerp_ = NULL; // new __m128[num_load1_*channels_]; + load1_x_ = new int[num_load1_]; + } else { + load1_offsets_ = NULL; + load1_shuffle_masks_ = NULL; + load1_mmxs_lerp_ = NULL; + load1_x_ = NULL; + } + if (num_load2_ > 0) { + load2_offsets_ = new int[num_load2_]; + load2_shuffle_masks_ = new __m128i[num_load2_ * channels_ * 2]; + load2_mmxs_lerp_ = NULL; // new __m128[num_load2_*channels_]; + load2_x_ = new int[num_load2_]; + } else { + load2_offsets_ = NULL; + load2_shuffle_masks_ = NULL; + load2_mmxs_lerp_ = NULL; + load2_x_ = NULL; + } + if (num_load4_ > 0) { + load4_offsets_ = new int[num_load4_ * 4]; + load4_mmxs_lerp_ = new __m128[num_load4_ * channels_]; + load4_x_ = new int[num_load4_]; + } else { + load4_offsets_ = NULL; + load4_mmxs_lerp_ = NULL; + load4_x_ = NULL; + } + if (num_load8_ > 0) { + load8_offsets_ = new int[num_load8_ * 4]; + load8_mmxs_lerp_ = new __m128[num_load8_ * channels_]; + load8_x_ = new int[num_load8_]; + } else { + load8_offsets_ = NULL; + load8_mmxs_lerp_ = NULL; + load8_x_ = NULL; + } + for (int i = 0; i < 5; ++i) num_cases[i] = 0; + if (verbose_) { + printf(" load4from1 = %d\n", num_load1_); + printf(" load4from2 = %d\n", num_load2_); + printf(" load4from4 = %d\n", num_load4_); + printf(" load4from8 = %d\n", num_load8_); + printf(" general = %d\n", num_general_); + } + for (int x = x0_; x <= x1_; ++x) { + int load_group = DetermineLoadGroup_(x); + assert(load_group >= 0 && load_group <= 4); + int current = num_cases[load_group]; + assert(current >= 0); + // printf(" ... load_group=%d, current=%d\n",load_group,current); + if (load_group == 0) { + // general case + assert(current < num_general_); + general_x_[current] = x; + } else if (load_group == 1) { + // load4from1 + assert(current < num_load1_); + load1_x_[current] = x; + int min_xidx, max_xidx; + ComputeXIndexRange_(x, &min_xidx, &max_xidx); + // printf(" ... x=%d, min_xidx=%d, max_xidx=%d\n",x,min_xidx,max_xidx); + load1_offsets_[current] = min_xidx * channels_; + float* xs_lerp = (float*)(load1_shuffle_masks_ + current * channels_ * 3); + char* shufmasks1 = + (char*)(load1_shuffle_masks_ + current * channels_ * 3 + channels_); + char* shufmasks2 = shufmasks1 + 16 * channels_; + for (int j = 0; j < 32 * channels_; ++j) shufmasks1[j] = -128; + for (int pix = 0; pix < 4; ++pix) { + const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) + : (x + pix) - min_ix_; + float lerp = xs_[ix].lerp; + int widx0 = xs_[ix].lower - + load1_offsets_[current]; // word index within SSE vector + // printf(" ..... pix_ix=%d, lerp=%f, widx0=%d\n",ix,lerp,widx0); + for (int ch = 0; ch < channels_; ++ch) { + int idx = pix * channels_ + ch; + xs_lerp[idx] = lerp; + int shufvec = idx / 4; + int shufidx = idx % 4; + int widx = widx0 + ch; + // printf(" ....... ch=%d, idx=%d, shufvec=%d, shufidx=%d, widx=%d, + // shufmasks1[%ld...]=...\n",ch,idx,shufvec,shufidx,widx,shufvec*16+shufidx*sizeof(T)); + for (int b = 0; b < sizeof(T); ++b) { + shufmasks1[shufvec * 16 + shufidx * sizeof(T) + b] = + widx * sizeof(T) + b; + shufmasks2[shufvec * 16 + shufidx * sizeof(T) + b] = + (widx + channels_) * sizeof(T) + b; + } + } + } + } else if (load_group == 2) { + // load4from2 + assert(current < num_load2_); + load2_x_[current] = x; + int min_xidx, max_xidx; + ComputeXIndexRange_(x, &min_xidx, &max_xidx); + load2_offsets_[current] = min_xidx * channels_; + float* xs_lerp = (float*)(load2_shuffle_masks_ + current * channels_ * 2); + char* shufmasks1 = + (char*)(load2_shuffle_masks_ + current * channels_ * 2 + channels_); + for (int j = 0; j < 16 * channels_; ++j) shufmasks1[j] = -128; + for (int pix = 0; pix < 4; ++pix) { + const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) + : (x + pix) - min_ix_; + float lerp = xs_[ix].lerp; + int widx0 = xs_[ix].lower - + load2_offsets_[current]; // word index within SSE vector + for (int ch = 0; ch < channels_; ++ch) { + int idx = pix * channels_ + ch; + xs_lerp[idx] = lerp; + int shufvec = idx / 4; + int shufidx = idx % 4; + int widx = widx0 + ch; + for (int b = 0; b < sizeof(T); ++b) { + shufmasks1[shufvec * 16 + shufidx * sizeof(T) + b] = + widx * sizeof(T) + b; + } + } + } + } else if (load_group == 3) { + // load4from4 + assert(current < num_load4_); + load4_x_[current] = x; + int* index = load4_offsets_ + current * 4; + float* xs_lerp = (float*)(load4_mmxs_lerp_ + current * channels_); + for (int pix = 0; pix < 4; ++pix) { + const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) + : (x + pix) - min_ix_; + float lerp = xs_[ix].lerp; + index[pix] = xs_[ix].lower; + for (int ch = 0; ch < channels_; ++ch) { + int idx = pix * channels_ + ch; + xs_lerp[idx] = lerp; + } + } + /* debug + printf("load4from4_%dch :: x=%d - + index={%ld",channels_,x,index[0]*sizeof(T)); + for (int i = 1; i < 4; ++i) printf(",%ld",index[i]*sizeof(T)); + printf("}\n"); + */ + } else if (load_group == 4) { + // load4from8 + assert(current < num_load8_); + load8_x_[current] = x; + int* index = load8_offsets_ + current * 4; + float* xs_lerp = (float*)(load8_mmxs_lerp_ + current * channels_); + for (int pix = 0; pix < 4; ++pix) { + const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) + : (x + pix) - min_ix_; + float lerp = xs_[ix].lerp; + index[pix] = xs_[ix].lower; + for (int ch = 0; ch < channels_; ++ch) { + int idx = pix * channels_ + ch; + xs_lerp[idx] = lerp; + } + } + /* debug + printf("x=%d :: load8_x_[%d] = %d",x,current,load8_x_[current]); + printf(", load8_offsets_[%d] = {%d",current*4,load8_offsets_[current*4]); + for (int pix = 1; pix < 4; ++pix) + printf(",%d",load8_offsets_[current*4+pix]); + printf("}"); + for (int ch = 0; ch < channels_; ++ch) { + float* p = (float*)(load8_mmxs_lerp_ + current * channels_ + ch); + printf(", lerp[%d] = {%.3f",current*channels_+ch,p[0]); + for (int j = 1; j < 4; ++j) printf(",%.3f",p[j]); + printf("}"); + } + printf("\n"); + */ + } else { + assert(false); + } + ++num_cases[load_group]; + // load_group == 0 -> general case, pixel by pixel + // every other value indidcates 1+3 = 4 pixels were processed this iteration + if (load_group > 0) x += 3; + } +} + +template +int CropResizeCastImage::DetermineLoadGroup_(const int x) { + int num_remaining = x1_ - x + 1; + if (num_remaining >= 4) { + // at least 4 values left, so theoretically possible to do SSE + int min_xidx, max_xidx; + // Using this-> is necessary in order to avoid compile error: + // "there are no arguments to ‘xxx’ that depend on a template parameter, so + // a declaration of ‘xxx’ must be available" + // This is an issue for all member functions that have only builtin type + // arguments and happens because + // argument dependent lookup is not done for these arguments (so I've been + // told). + if (this->ComputeXIndexRange_(x, &min_xidx, &max_xidx)) { + if ((allowed_load_groups_ & 1) && this->Load1_ok_(min_xidx, max_xidx)) { + return 1; + } else if ((allowed_load_groups_ & 2) && + this->Load2_ok_(min_xidx, max_xidx)) { + return 2; + } else if ((allowed_load_groups_ & 4) && + this->Load4_ok_(min_xidx, max_xidx)) { + return 3; + } else if ((allowed_load_groups_ & 8) && + this->Load8_ok_(min_xidx, max_xidx)) { + return 4; + } else { + return 0; + } + } else { + // assumption xs[i].lower + channels == xs[i].upper NOT true for this + // quintuple. + return 0; + } + } else { + // too few remaining values + return 0; + } +} + +// Compute range of x indexes for xs[0] through xs[3]. +// Returns true if valid (xs[i].lower + channels == xs[i].upper for all pixels). +template +bool CropResizeCastImage::ComputeXIndexRange_(const int x, int* min_xidx, + int* max_xidx) { + bool upper_is_lower_plus_one = true; + *min_xidx = 0; + *max_xidx = -1; + for (int pix = 0; pix < 4; ++pix) { + const int ix = + flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) : (x + pix) - min_ix_; + int curr_xidx = xs_[ix].lower; + if (curr_xidx + channels_ == xs_[ix].upper) { + if (pix == 0) { + *min_xidx = curr_xidx; + *max_xidx = curr_xidx; + } else { + if (curr_xidx < *min_xidx) *min_xidx = curr_xidx; + if (curr_xidx > *max_xidx) *max_xidx = curr_xidx; + } + } else { + upper_is_lower_plus_one = false; + } + } + *min_xidx /= channels_; + *max_xidx /= channels_; + return upper_is_lower_plus_one; +} + +// This method returns true if it is possible to do load4from1 +// for the load group pointed to by xs. +template +bool CropResizeCastImage::Load1_ok_(const int min_xidx, + const int max_xidx) { + // num_pixels_to_load_left_input = max_xs_low - min_xs_low + 1 + // num_pixels_to_load_left_and_right_input = num_pixels_to_load_left_input + 1 + int total_load_bytes = (max_xidx - min_xidx + 2) * channels_ * sizeof(T); + if (total_load_bytes <= 16) { + // a single (mis-aligned) SSE word gives us all the inputs + // ensure that SSE word can be loaded without causing SEGV + int load_offset = min_xidx * channels_; + int load_offset_bytes = load_offset * sizeof(T); + if (in_row_size_bytes_ - load_offset_bytes >= 16) { + return true; + } else { + return false; + } + } else { + return false; + } +} + +// This method returns true if it is possible to do load4from2 +// for the load group pointed to by xs. +template +bool CropResizeCastImage::Load2_ok_(const int min_xidx, + const int max_xidx) { + // num_pixels_to_load_left_input = max_xs_low - min_xs_low + 1 + int total_load_bytes = (max_xidx - min_xidx + 1) * channels_ * sizeof(T); + if (total_load_bytes <= 16) { + // a single (mis-aligned) SSE word gives us all the inputs + // ensure that SSE word can be loaded without causing SEGV + int load_offset = (min_xidx + 1) * channels_; + int load_offset_bytes = load_offset * sizeof(T); + if (in_row_size_bytes_ - load_offset_bytes >= 16) { + return true; + } else { + return false; + } + } else { + return false; + } +} + +// This method returns true if it is possible to do load4from4 +// for the load group pointed to by xs. +template +bool CropResizeCastImage::Load4_ok_(const int min_xidx, + const int max_xidx) { + int total_load_bytes = 2 * channels_ * sizeof(T); + if (total_load_bytes <= 16) { + // ensure that SSE word can be loaded without causing SEGV + int load_offset = max_xidx * channels_; + int load_offset_bytes = load_offset * sizeof(T); + if (in_row_size_bytes_ - load_offset_bytes >= 16) { + return true; + } else { + return false; + } + } else { + return false; + } +} + +// This method returns true if it is possible to do load4from8 +// for the load group pointed to by xs. +template +bool CropResizeCastImage::Load8_ok_(const int min_xidx, + const int max_xidx) { + int total_load_bytes = channels_ * sizeof(T); + if (total_load_bytes <= 16) { + // ensure that SSE word can be loaded without causing SEGV + int load_offset = (max_xidx + 1) * channels_; + int load_offset_bytes = load_offset * sizeof(T); + if (in_row_size_bytes_ - load_offset_bytes >= 16) { + return true; + } else { + return false; + } + } else { + return false; + } +} + +// +// full implementations of templated static member function clip_necessary() +// + +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} + +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} + +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} + +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} + +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} + +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} + +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return true; +} +template <> +bool CropResizeCastImage::clip_necessary() { + return false; +} + +#endif // __SSE4_1__ + +template +void crop_resize_single_image_common( + const T* image, const int64 in_height, const int64 in_width, + const int64 out_height, const int64 out_width, const int channels, + const int min_ix, const int max_ix, const CachedInterpolation* xs, + const int min_iy, const int max_iy, const CachedInterpolation* ys, + const float extrapolated_value, const bool flip_x, const bool flip_y, + U* output) TF_ATTRIBUTE_NOINLINE; + +#ifdef __SSE4_1__ + +// full specializations of crop_resize_single_image_common for data types that +// have vectorized implementations. +// at the moment, this is uint8, int8, uint16, int16, int32, Eigen::half, +// bfloat16 and float. + +#define CROP_RESIZE_SINGLE_IMAGE_VECT(T_type, U_type) \ + template <> \ + void crop_resize_single_image_common( \ + const T_type* image, const int64 in_height, const int64 in_width, \ + const int64 out_height, const int64 out_width, const int channels, \ + const int min_ix, const int max_ix, const CachedInterpolation* xs, \ + const int min_iy, const int max_iy, const CachedInterpolation* ys, \ + const float extrapolated_value, const bool flip_x, const bool flip_y, \ + U_type* output) { \ + if (channels <= 4) { \ + CropResizeCastImage* resizer = \ + new CropResizeCastImage( \ + in_height, in_width, out_height, out_width, channels, min_ix, \ + max_ix, xs, min_iy, max_iy, ys, extrapolated_value, flip_x, \ + flip_y, false, 15); \ + resizer->Resize(image, output); \ + delete resizer; \ + } else { \ + crop_resize_single_image(image, in_height, in_width, out_height, \ + out_width, channels, min_ix, max_ix, xs, \ + min_iy, max_iy, ys, extrapolated_value, flip_x, \ + flip_y, output); \ + } \ + } + +CROP_RESIZE_SINGLE_IMAGE_VECT(uint8, float) +CROP_RESIZE_SINGLE_IMAGE_VECT(int8, float) +CROP_RESIZE_SINGLE_IMAGE_VECT(uint16, float) +CROP_RESIZE_SINGLE_IMAGE_VECT(int16, float) +CROP_RESIZE_SINGLE_IMAGE_VECT(int32, float) +CROP_RESIZE_SINGLE_IMAGE_VECT(Eigen::half, float) +CROP_RESIZE_SINGLE_IMAGE_VECT(bfloat16, float) +CROP_RESIZE_SINGLE_IMAGE_VECT(float, float) + +// full specializations of crop_resize_single_image_common for data types that +// don't have vectorized implementations. +// image resizing for these data types default to the original code. +// at the moment, this is int64 and double. + +#define CROP_RESIZE_SINGLE_IMAGE_REGULAR(T_type, U_type) \ + template <> \ + void crop_resize_single_image_common( \ + const T_type* image, const int64 in_height, const int64 in_width, \ + const int64 out_height, const int64 out_width, const int channels, \ + const int min_ix, const int max_ix, const CachedInterpolation* xs, \ + const int min_iy, const int max_iy, const CachedInterpolation* ys, \ + const float extrapolated_value, const bool flip_x, const bool flip_y, \ + U_type* output) { \ + crop_resize_single_image(image, in_height, in_width, out_height, \ + out_width, channels, min_ix, max_ix, xs, min_iy, \ + max_iy, ys, extrapolated_value, flip_x, flip_y, \ + output); \ + } + +CROP_RESIZE_SINGLE_IMAGE_REGULAR(int64, float) +CROP_RESIZE_SINGLE_IMAGE_REGULAR(double, float) + +#else + +// the vectorized implementations need at least SSE4.1 to compile. +// if that is not enabled, default to original code. + +template +void crop_resize_single_image_common( + const T* image, const int64 in_height, const int64 in_width, + const int64 out_height, const int64 out_width, const int channels, + const int min_ix, const int max_ix, const CachedInterpolation* xs, + const int min_iy, const int max_iy, const CachedInterpolation* ys, + const float extrapolated_value, const bool flip_x, const bool flip_y, + U* output) { + crop_resize_single_image(image, in_height, in_width, out_height, out_width, + channels, min_ix, max_ix, xs, min_iy, max_iy, ys, + extrapolated_value, flip_x, flip_y, output); +} + +#endif + +} // namespace +} // namespace tensorflow +#endif // define TENSORFLOW_CORE_KERNELS_CROP_RESIZE_BILINEAR_CORE_H_ diff --git a/tensorflow/core/kernels/resize_bilinear_op.cc b/tensorflow/core/kernels/resize_bilinear_op.cc index f10c9a19a7..5cc8799346 100644 --- a/tensorflow/core/kernels/resize_bilinear_op.cc +++ b/tensorflow/core/kernels/resize_bilinear_op.cc @@ -19,15 +19,16 @@ limitations under the License. #include "tensorflow/core/kernels/resize_bilinear_op.h" #include -#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/framework/types.h" +#include "tensorflow/core/kernels/crop_resize_bilinear_core.h" #include "tensorflow/core/kernels/image_resizer_state.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/platform/logging.h" +#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" namespace tensorflow { @@ -63,140 +64,6 @@ class ResizeBilinearOp : public OpKernel { bool align_corners_; }; -namespace { -// Compute the interpolation indices only once. -struct CachedInterpolation { - int64 lower; // Lower source index used in the interpolation - int64 upper; // Upper source index used in the interpolation - // 1-D linear iterpolation scale (see: - // https://en.wikipedia.org/wiki/Bilinear_interpolation) - float lerp; -}; - -inline void compute_interpolation_weights(const int64 out_size, - const int64 in_size, - const float scale, - CachedInterpolation* interpolation) { - interpolation[out_size].lower = 0; - interpolation[out_size].upper = 0; - for (int64 i = out_size - 1; i >= 0; --i) { - const float in = i * scale; - interpolation[i].lower = static_cast(in); - interpolation[i].upper = std::min(interpolation[i].lower + 1, in_size - 1); - interpolation[i].lerp = in - interpolation[i].lower; - } -} - -/** - * Computes the bilinear interpolation from the appropriate 4 float points - * and the linear interpolation weights. - */ -inline float compute_lerp(const float top_left, const float top_right, - const float bottom_left, const float bottom_right, - const float x_lerp, const float y_lerp) { - const float top = top_left + (top_right - top_left) * x_lerp; - const float bottom = bottom_left + (bottom_right - bottom_left) * x_lerp; - return top + (bottom - top) * y_lerp; -} - -template -void resize_image( - typename TTypes::ConstTensor images, const int batch_size, - const int64 in_height, const int64 in_width, const int64 out_height, - const int64 out_width, const int channels, - const std::vector& xs, - const std::vector& ys, - typename TTypes::Tensor output) TF_ATTRIBUTE_NOINLINE; -template -void resize_image(typename TTypes::ConstTensor images, - const int batch_size, const int64 in_height, - const int64 in_width, const int64 out_height, - const int64 out_width, const int channels, - const std::vector& xs_vec, - const std::vector& ys, - typename TTypes::Tensor output) { - const int64 in_row_size = in_width * channels; - const int64 in_batch_num_values = in_height * in_row_size; - const int64 out_row_size = out_width * channels; - - const T* input_b_ptr = images.data(); - const CachedInterpolation* xs = xs_vec.data(); - - if (channels == 3) { - float* output_y_ptr = output.data(); - for (int b = 0; b < batch_size; ++b) { - for (int64 y = 0; y < out_height; ++y) { - const T* ys_input_lower_ptr = input_b_ptr + ys[y].lower * in_row_size; - const T* ys_input_upper_ptr = input_b_ptr + ys[y].upper * in_row_size; - const float ys_lerp = ys[y].lerp; - for (int64 x = 0; x < out_width; ++x) { - const int64 xs_lower = xs[x].lower; - const int64 xs_upper = xs[x].upper; - const float xs_lerp = xs[x].lerp; - - // Read channel 0. - const float top_left0(ys_input_lower_ptr[xs_lower + 0]); - const float top_right0(ys_input_lower_ptr[xs_upper + 0]); - const float bottom_left0(ys_input_upper_ptr[xs_lower + 0]); - const float bottom_right0(ys_input_upper_ptr[xs_upper + 0]); - - // Read channel 1. - const float top_left1(ys_input_lower_ptr[xs_lower + 1]); - const float top_right1(ys_input_lower_ptr[xs_upper + 1]); - const float bottom_left1(ys_input_upper_ptr[xs_lower + 1]); - const float bottom_right1(ys_input_upper_ptr[xs_upper + 1]); - - // Read channel 2. - const float top_left2(ys_input_lower_ptr[xs_lower + 2]); - const float top_right2(ys_input_lower_ptr[xs_upper + 2]); - const float bottom_left2(ys_input_upper_ptr[xs_lower + 2]); - const float bottom_right2(ys_input_upper_ptr[xs_upper + 2]); - - // Compute output. - output_y_ptr[x * channels + 0] = - compute_lerp(top_left0, top_right0, bottom_left0, bottom_right0, - xs_lerp, ys_lerp); - output_y_ptr[x * channels + 1] = - compute_lerp(top_left1, top_right1, bottom_left1, bottom_right1, - xs_lerp, ys_lerp); - output_y_ptr[x * channels + 2] = - compute_lerp(top_left2, top_right2, bottom_left2, bottom_right2, - xs_lerp, ys_lerp); - } - output_y_ptr += out_row_size; - } - input_b_ptr += in_batch_num_values; - } - } else { - float* output_y_ptr = output.data(); - for (int b = 0; b < batch_size; ++b) { - for (int64 y = 0; y < out_height; ++y) { - const T* ys_input_lower_ptr = input_b_ptr + ys[y].lower * in_row_size; - const T* ys_input_upper_ptr = input_b_ptr + ys[y].upper * in_row_size; - const float ys_lerp = ys[y].lerp; - for (int64 x = 0; x < out_width; ++x) { - auto xs_lower = xs[x].lower; - auto xs_upper = xs[x].upper; - auto xs_lerp = xs[x].lerp; - for (int c = 0; c < channels; ++c) { - const float top_left(ys_input_lower_ptr[xs_lower + c]); - const float top_right(ys_input_lower_ptr[xs_upper + c]); - const float bottom_left(ys_input_upper_ptr[xs_lower + c]); - const float bottom_right(ys_input_upper_ptr[xs_upper + c]); - output_y_ptr[x * channels + c] = - compute_lerp(top_left, top_right, bottom_left, bottom_right, - xs_lerp, ys_lerp); - } - } - output_y_ptr += out_row_size; - } - input_b_ptr += in_batch_num_values; - } - } -} - -} // namespace - // Partial specialization of ResizeBilinear functor for a CPUDevice. namespace functor { template @@ -212,6 +79,11 @@ struct ResizeBilinear { const int64 out_height = output.dimension(1); const int64 out_width = output.dimension(2); + const int64 in_row_size = in_width * channels; + const int64 in_batch_num_values = in_height * in_row_size; + const int64 out_row_size = out_width * channels; + const int64 out_batch_num_values = out_row_size * out_height; + // Handle no-op resizes efficiently. if (out_height == in_height && out_width == in_width) { output = images.template cast(); @@ -232,8 +104,13 @@ struct ResizeBilinear { xs[i].upper *= channels; } - resize_image(images, batch_size, in_height, in_width, out_height, - out_width, channels, xs, ys, output); + for (int b = 0; b < batch_size; ++b) { + crop_resize_single_image_common( + images.data() + (int64)b * in_batch_num_values, in_height, in_width, + out_height, out_width, channels, 0, out_width - 1, xs.data(), 0, + out_height - 1, ys.data(), 0.0f, false, false, + output.data() + (int64)b * out_batch_num_values); + } } }; } // namespace functor diff --git a/tensorflow/core/kernels/resize_bilinear_op_test.cc b/tensorflow/core/kernels/resize_bilinear_op_test.cc index 6d57892828..55e1d2e1e2 100644 --- a/tensorflow/core/kernels/resize_bilinear_op_test.cc +++ b/tensorflow/core/kernels/resize_bilinear_op_test.cc @@ -122,7 +122,7 @@ class ResizeBilinearOpTest : public OpsTestBase { TensorShape({batch_size, output_width, output_height, channels}))); ResizeBilinearBaseline(input->tensor(), expected->tensor()); - test::ExpectTensorEqual(*expected, *GetOutput(0)); + test::ExpectClose(*expected, *GetOutput(0)); } void RunManyRandomTests(int channels) { -- GitLab From d3b4d3c8ffd52da2c094f58a728209c6a76f4b66 Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Fri, 7 Sep 2018 22:07:01 -0700 Subject: [PATCH 0012/1825] More FMA's --- .../core/kernels/crop_resize_bilinear_core.h | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tensorflow/core/kernels/crop_resize_bilinear_core.h b/tensorflow/core/kernels/crop_resize_bilinear_core.h index f6846d6a55..0209130b2c 100644 --- a/tensorflow/core/kernels/crop_resize_bilinear_core.h +++ b/tensorflow/core/kernels/crop_resize_bilinear_core.h @@ -4023,9 +4023,15 @@ void CropResizeCastImage::ResizeRow_load1_1ch_( __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); #endif +#ifdef __AVX2__ + __m128 res[1]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + this->write_1ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); +#else __m128 res[1]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); this->write_1ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from2' group for an entire row of a @@ -4059,9 +4065,15 @@ void CropResizeCastImage::ResizeRow_load2_1ch_( __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); #endif +#ifdef __AVX2__ + __m128 res[1]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + this->write_1ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); +#else __m128 res[1]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); this->write_1ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from4' group for an entire row of a @@ -4096,9 +4108,15 @@ void CropResizeCastImage::ResizeRow_load4_1ch_( __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); #endif +#ifdef __AVX2__ + __m128 res[1]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + this->write_1ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); +#else __m128 res[1]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); this->write_1ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from8' group for an entire row of a @@ -4133,9 +4151,15 @@ void CropResizeCastImage::ResizeRow_load8_1ch_( __m128 top0 = _mm_add_ps(tl0, _mm_mul_ps(x_lerp, _mm_sub_ps(tr0, tl0))); __m128 bot0 = _mm_add_ps(bl0, _mm_mul_ps(x_lerp, _mm_sub_ps(br0, bl0))); #endif +#ifdef __AVX2__ + __m128 res[1]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + this->write_1ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); +#else __m128 res[1]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); this->write_1ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); +#endif } } #undef CHANNELS @@ -4181,10 +4205,17 @@ void CropResizeCastImage::ResizeRow_load1_2ch_( __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); #endif +#ifdef __AVX2__ + __m128 res[2]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + this->write_2ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); +#else __m128 res[2]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); this->write_2ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from2' group for an entire row of a @@ -4227,10 +4258,17 @@ void CropResizeCastImage::ResizeRow_load2_2ch_( __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); #endif +#ifdef __AVX2__ + __m128 res[2]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + this->write_2ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); +#else __m128 res[2]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); this->write_2ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from4' group for an entire row of a @@ -4274,10 +4312,17 @@ void CropResizeCastImage::ResizeRow_load4_2ch_( __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); #endif +#ifdef __AVX2__ + __m128 res[2]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + this->write_2ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); +#else __m128 res[2]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); this->write_2ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from8' group for an entire row of a @@ -4321,10 +4366,17 @@ void CropResizeCastImage::ResizeRow_load8_2ch_( __m128 top1 = _mm_add_ps(tl1, _mm_mul_ps(x_lerp, _mm_sub_ps(tr1, tl1))); __m128 bot1 = _mm_add_ps(bl1, _mm_mul_ps(x_lerp, _mm_sub_ps(br1, bl1))); #endif +#ifdef __AVX2__ + __m128 res[2]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + this->write_2ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); +#else __m128 res[2]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); this->write_2ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); +#endif } } #undef CHANNELS @@ -4378,11 +4430,19 @@ void CropResizeCastImage::ResizeRow_load1_3ch_( __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); #endif +#ifdef __AVX2__ + __m128 res[3]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + res[2] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot2, top2), top2); + this->write_3ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); +#else __m128 res[3]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); this->write_3ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from2' group for an entire row of a @@ -4433,11 +4493,19 @@ void CropResizeCastImage::ResizeRow_load2_3ch_( __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); #endif +#ifdef __AVX2__ + __m128 res[3]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + res[2] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot2, top2), top2); + this->write_3ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); +#else __m128 res[3]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); this->write_3ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from4' group for an entire row of a @@ -4490,11 +4558,19 @@ void CropResizeCastImage::ResizeRow_load4_3ch_( __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); #endif +#ifdef __AVX2__ + __m128 res[3]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + res[2] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot2, top2), top2); + this->write_3ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); +#else __m128 res[3]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); this->write_3ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from8' group for an entire row of a @@ -4547,11 +4623,19 @@ void CropResizeCastImage::ResizeRow_load8_3ch_( __m128 top2 = _mm_add_ps(tl2, _mm_mul_ps(x_lerp, _mm_sub_ps(tr2, tl2))); __m128 bot2 = _mm_add_ps(bl2, _mm_mul_ps(x_lerp, _mm_sub_ps(br2, bl2))); #endif +#ifdef __AVX2__ + __m128 res[3]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + res[2] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot2, top2), top2); + this->write_3ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); +#else __m128 res[3]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); this->write_3ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); +#endif } } #undef CHANNELS @@ -4615,12 +4699,21 @@ void CropResizeCastImage::ResizeRow_load1_4ch_( __m128 top3 = _mm_add_ps(tl3, _mm_mul_ps(x_lerp, _mm_sub_ps(tr3, tl3))); __m128 bot3 = _mm_add_ps(bl3, _mm_mul_ps(x_lerp, _mm_sub_ps(br3, bl3))); #endif +#ifdef __AVX2__ + __m128 res[4]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + res[2] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot2, top2), top2); + res[3] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot3, top3), top3); + this->write_4ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); +#else __m128 res[4]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); res[3] = _mm_add_ps(top3, _mm_mul_ps(y_lerp, _mm_sub_ps(bot3, top3))); this->write_4ch(ysA_output_ptr + load1_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from2' group for an entire row of a @@ -4681,12 +4774,21 @@ void CropResizeCastImage::ResizeRow_load2_4ch_( __m128 top3 = _mm_add_ps(tl3, _mm_mul_ps(x_lerp, _mm_sub_ps(tr3, tl3))); __m128 bot3 = _mm_add_ps(bl3, _mm_mul_ps(x_lerp, _mm_sub_ps(br3, bl3))); #endif +#ifdef __AVX2__ + __m128 res[4]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + res[2] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot2, top2), top2); + res[3] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot3, top3), top3); + this->write_4ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); +#else __m128 res[4]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); res[3] = _mm_add_ps(top3, _mm_mul_ps(y_lerp, _mm_sub_ps(bot3, top3))); this->write_4ch(ysA_output_ptr + load2_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from4' group for an entire row of a @@ -4748,12 +4850,21 @@ void CropResizeCastImage::ResizeRow_load4_4ch_( __m128 top3 = _mm_add_ps(tl3, _mm_mul_ps(x_lerp, _mm_sub_ps(tr3, tl3))); __m128 bot3 = _mm_add_ps(bl3, _mm_mul_ps(x_lerp, _mm_sub_ps(br3, bl3))); #endif +#ifdef __AVX2__ + __m128 res[4]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + res[2] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot2, top2), top2); + res[3] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot3, top3), top3); + this->write_4ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); +#else __m128 res[4]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); res[3] = _mm_add_ps(top3, _mm_mul_ps(y_lerp, _mm_sub_ps(bot3, top3))); this->write_4ch(ysA_output_ptr + load4_x_[current] * CHANNELS, res); +#endif } } // Resize all points that fall in the 'load4from8' group for an entire row of a @@ -4815,12 +4926,21 @@ void CropResizeCastImage::ResizeRow_load8_4ch_( __m128 top3 = _mm_add_ps(tl3, _mm_mul_ps(x_lerp, _mm_sub_ps(tr3, tl3))); __m128 bot3 = _mm_add_ps(bl3, _mm_mul_ps(x_lerp, _mm_sub_ps(br3, bl3))); #endif +#ifdef __AVX2__ + __m128 res[4]; + res[0] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot0, top0), top0); + res[1] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot1, top1), top1); + res[2] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot2, top2), top2); + res[3] = _mm_fmadd_ps(y_lerp, _mm_sub_ps(bot3, top3), top3); + this->write_4ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); +#else __m128 res[4]; res[0] = _mm_add_ps(top0, _mm_mul_ps(y_lerp, _mm_sub_ps(bot0, top0))); res[1] = _mm_add_ps(top1, _mm_mul_ps(y_lerp, _mm_sub_ps(bot1, top1))); res[2] = _mm_add_ps(top2, _mm_mul_ps(y_lerp, _mm_sub_ps(bot2, top2))); res[3] = _mm_add_ps(top3, _mm_mul_ps(y_lerp, _mm_sub_ps(bot3, top3))); this->write_4ch(ysA_output_ptr + load8_x_[current] * CHANNELS, res); +#endif } } #undef CHANNELS -- GitLab From 318d2dd306cc221a51346a076272fbdc10ebdab9 Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Tue, 11 Sep 2018 12:48:22 -0700 Subject: [PATCH 0013/1825] Swap copts and prefix lines to make UBUNTU sanity check happy --- tensorflow/core/kernels/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 3b2b71ec2a..a2b2432e02 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -2156,8 +2156,8 @@ tf_kernel_library( tf_kernel_library( name = "crop_and_resize_op", - prefix = "crop_and_resize_op", copts = tf_copts() + if_linux_x86_64(["-msse4.1 -finline-functions"]), + prefix = "crop_and_resize_op", deps = IMAGE_DEPS + [":crop_resize_bilinear_core"], ) @@ -2223,8 +2223,8 @@ tf_kernel_library( tf_kernel_library( name = "resize_bilinear_op", - prefix = "resize_bilinear_op", copts = tf_copts() + if_linux_x86_64(["-msse4.1 -finline-functions"]), + prefix = "resize_bilinear_op", deps = IMAGE_DEPS + [":crop_resize_bilinear_core"], ) -- GitLab From ac4d6c3d914893d5ea0d4b25ee5ceeb6a6d51b42 Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Tue, 11 Sep 2018 14:08:46 -0700 Subject: [PATCH 0014/1825] Fix android build --- tensorflow/core/kernels/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index a2b2432e02..6bcc360740 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -5197,6 +5197,7 @@ filegroup( "population_count_op.h", "winograd_transform.h", ":android_extended_ops_headers", + ":crop_resize_bilinear_core", ] + select({ ":xsmm_convolutions": [ "xsmm_conv2d.h", @@ -5291,6 +5292,7 @@ filegroup( "where_op.cc", "xent_op.cc", ":android_extended_ops_headers", + ":crop_resize_bilinear_core", ], ) -- GitLab From 3f642153f02b0ce9910a4a7970ec4a961b827c86 Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Tue, 11 Sep 2018 14:54:25 -0700 Subject: [PATCH 0015/1825] Did not know that android targets must start with android, try again to fix android DEMO build --- tensorflow/core/kernels/BUILD | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 6bcc360740..98e1f0ab13 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -5069,6 +5069,7 @@ filegroup( "control_flow_ops.h", "conv_2d.h", "conv_ops.h", + "crop_resize_bilinear_core.h", "data_format_ops.h", "depthtospace_op.h", "depthwise_conv_op.h", @@ -5197,7 +5198,6 @@ filegroup( "population_count_op.h", "winograd_transform.h", ":android_extended_ops_headers", - ":crop_resize_bilinear_core", ] + select({ ":xsmm_convolutions": [ "xsmm_conv2d.h", @@ -5292,7 +5292,6 @@ filegroup( "where_op.cc", "xent_op.cc", ":android_extended_ops_headers", - ":crop_resize_bilinear_core", ], ) -- GitLab From 1253ef6970edccb21c4d4995a4af9103c0cd2b88 Mon Sep 17 00:00:00 2001 From: "Li, Guizi" Date: Fri, 31 Aug 2018 14:10:05 +0800 Subject: [PATCH 0016/1825] [Intel-MKL] Enable MKL Relu6 op --- tensorflow/core/graph/mkl_layout_pass.cc | 10 ++ tensorflow/core/graph/mkl_layout_pass_test.cc | 73 +++++++++++ tensorflow/core/kernels/mkl_relu_op.cc | 120 +++++++++++++++--- tensorflow/core/ops/nn_ops.cc | 31 +++++ 4 files changed, 217 insertions(+), 17 deletions(-) diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc index 2e644fe987..a2e0072011 100644 --- a/tensorflow/core/graph/mkl_layout_pass.cc +++ b/tensorflow/core/graph/mkl_layout_pass.cc @@ -2444,6 +2444,8 @@ class MklLayoutRewritePass : public GraphOptimizationPass { "_MklConv2DBackpropFilterWithBias"; csinfo_.relu = "Relu"; csinfo_.relu_grad = "ReluGrad"; + csinfo_.relu6 = "Relu6"; + csinfo_.relu6_grad = "Relu6Grad"; csinfo_.tanh = "Tanh"; csinfo_.tanh_grad = "TanhGrad"; csinfo_.reshape = "Reshape"; @@ -2543,6 +2545,12 @@ class MklLayoutRewritePass : public GraphOptimizationPass { rinfo_.push_back({csinfo_.relu_grad, mkl_op_registry::GetMklOpName(csinfo_.relu_grad), CopyAttrsDataType, AlwaysRewrite}); + rinfo_.push_back({csinfo_.relu6, + mkl_op_registry::GetMklOpName(csinfo_.relu6), + CopyAttrsDataType, AlwaysRewrite}); + rinfo_.push_back({csinfo_.relu6_grad, + mkl_op_registry::GetMklOpName(csinfo_.relu6_grad), + CopyAttrsDataType, AlwaysRewrite}); /* rinfo_.push_back({csinfo_.tanh, mkl_op_registry::GetMklOpName(csinfo_.tanh), @@ -2670,6 +2678,8 @@ class MklLayoutRewritePass : public GraphOptimizationPass { string mul; string relu; string relu_grad; + string relu6; + string relu6_grad; string tanh; string tanh_grad; string reshape; diff --git a/tensorflow/core/graph/mkl_layout_pass_test.cc b/tensorflow/core/graph/mkl_layout_pass_test.cc index e8bac847e5..09ed5b660e 100644 --- a/tensorflow/core/graph/mkl_layout_pass_test.cc +++ b/tensorflow/core/graph/mkl_layout_pass_test.cc @@ -2777,6 +2777,52 @@ TEST_F(MklLayoutPassTest, NodeRewrite_ReluReluGrad_Positive) { "DMT/_1->C:2"); } +TEST_F(MklLayoutPassTest, NodeRewrite_Relu6_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Relu6'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A'] }" + "node { name: 'C' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'B'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(_MklRelu6);C(Zeta);DMT/_0(Const)|A->B;A->C;" + "A:control->DMT/_0:control;B->C:1;DMT/_0->B:1"); +} + +TEST_F(MklLayoutPassTest, NodeRewrite_Relu6Grad_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Input'}" + "node { name: 'C' op: 'Relu6Grad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'B'] }" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Input);C(_MklRelu6Grad);D(Zeta);DMT/_0(Const);" + "DMT/_1(Const)|A->C;A->D;A:control->DMT/_0:control;" + "A:control->DMT/_1:control;B->C:1;C->D:1;DMT/_0->C:2;DMT/_1->C:3"); +} + +TEST_F(MklLayoutPassTest, NodeRewrite_Relu6Relu6Grad_Positive) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Relu6'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A'] }" + "node { name: 'C' op: 'Relu6Grad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'B'] }" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'C'] }"); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(_MklRelu6);C(_MklRelu6Grad);D(Zeta);DMT/_0(Const);" + "DMT/_1(Const)|A->B;A->C;A->D;A:control->DMT/_0:control;" + "A:control->DMT/_1:control;B->C:1;B:1->C:3;C->D:1;DMT/_0->B:1;" + "DMT/_1->C:2"); +} + TEST_F(MklLayoutPassTest, NodeRewrite_AvgPool_Positive) { InitGraph( "node { name: 'A' op: 'Input'}" @@ -3378,6 +3424,33 @@ TEST_F(MklLayoutPassTest, NodeRewrite_ReluGrad_DeviceTest) { "A(Input);B(Input);C(ReluGrad);D(Zeta)|A->C;A->D;B->C:1;C->D:1"); } +TEST_F(MklLayoutPassTest, NodeRewrite_Relu6_DeviceTest) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Relu6'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A'] }" + "node { name: 'C' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'B'] }", + kGPUDevice); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Relu6);C(Zeta)|A->B;A->C;B->C:1"); +} + +TEST_F(MklLayoutPassTest, NodeRewrite_Relu6Grad_DeviceTest) { + InitGraph( + "node { name: 'A' op: 'Input'}" + "node { name: 'B' op: 'Input'}" + "node { name: 'C' op: 'Relu6Grad'" + " attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'B'] }" + "node { name: 'D' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }" + " input: ['A', 'C'] }", + kGPUDevice); + EXPECT_EQ(DoMklLayoutOptimizationPass(), + "A(Input);B(Input);C(Relu6Grad);D(Zeta)|A->C;A->D;B->C:1;C->D:1"); +} + TEST_F(MklLayoutPassTest, NodeRewrite_MaxPool_DeviceTest) { InitGraph( "node { name: 'A' op: 'Input'}" diff --git a/tensorflow/core/kernels/mkl_relu_op.cc b/tensorflow/core/kernels/mkl_relu_op.cc index 84385356e1..e010810827 100644 --- a/tensorflow/core/kernels/mkl_relu_op.cc +++ b/tensorflow/core/kernels/mkl_relu_op.cc @@ -30,6 +30,7 @@ using mkldnn::algorithm; using mkldnn::eltwise_elu; using mkldnn::eltwise_relu; using mkldnn::eltwise_tanh; +using mkldnn::eltwise_bounded_relu; using mkldnn::memory; using mkldnn::prop_kind; using mkldnn::relu_backward; @@ -785,7 +786,8 @@ class MklReluOpBase : public OpKernel { public: ~MklReluOpBase() {} - explicit MklReluOpBase(OpKernelConstruction* context) : OpKernel(context) {} + explicit MklReluOpBase(OpKernelConstruction* context, float alpha, float beta) + : OpKernel(context), alpha_(alpha), beta_(beta) {} virtual void Compute_Scalar(OpKernelContext* context) = 0; void Compute(OpKernelContext* context) override { @@ -815,10 +817,9 @@ class MklReluOpBase : public OpKernel { src_md = MklDnnData::CreateBlockedMemDesc(src_dims, src_strides); } - T alpha = 0, beta = 0; - // get a eltwise fwd from primitive pool - MklEltwiseFwdParams fwdParams(src_dims, src_md, alg_kind, alpha, beta); + MklEltwiseFwdParams fwdParams(src_dims, src_md, alg_kind, alpha_, + beta_); MklEltwiseFwdPrimitive* eltwise_fwd = MklEltwiseFwdPrimitiveFactory::Get(fwdParams); @@ -879,6 +880,8 @@ class MklReluOpBase : public OpKernel { private: engine cpu_engine = engine(engine::cpu, 0); std::shared_ptr relu_fwd_pd; + float alpha_; + float beta_; }; template @@ -886,9 +889,9 @@ class MklReluGradOpBase : public OpKernel { public: ~MklReluGradOpBase() {} - explicit MklReluGradOpBase(OpKernelConstruction* context) - : OpKernel(context) { - } + explicit MklReluGradOpBase(OpKernelConstruction* context, float alpha, + float beta) + : OpKernel(context), alpha_(alpha), beta_(beta) {} virtual void Compute_Scalar(OpKernelContext* context) = 0; @@ -958,8 +961,6 @@ class MklReluGradOpBase : public OpKernel { src_dims = dnn_shape_src.GetSizesAsMklDnnDims(); } - T alpha = 0, beta = 0; - // As per comment above, we tell MKLDNN that both the inputs are in same // format. So we set common memory descriptor in MKL format, if any of the // inputs are in MKL format. Let's get memory descriptor that we will use @@ -973,8 +974,8 @@ class MklReluGradOpBase : public OpKernel { common_md = src_md; } - MklEltwiseBwdParams bwdParams(src_dims, common_md, alg_kind, alpha, - beta); + MklEltwiseBwdParams bwdParams(src_dims, common_md, alg_kind, alpha_, + beta_); MklEltwiseBwdPrimitive* eltwise_bwd = MklEltwiseBwdPrimitiveFactory::Get(bwdParams); auto eltwise_bwd_pd = eltwise_bwd->GetEltwiseBwdPd(); @@ -1044,6 +1045,8 @@ class MklReluGradOpBase : public OpKernel { private: engine cpu_engine = engine(engine::cpu, 0); std::shared_ptr relu_fwd_pd; + float alpha_; + float beta_; }; template @@ -1052,7 +1055,7 @@ class MklReluOp : public MklReluOpBase { ~MklReluOp() {} explicit MklReluOp(OpKernelConstruction* context) - : MklReluOpBase(context) {} + : MklReluOpBase(context, 0.0f, 0.0f) {} virtual void Compute_Scalar(OpKernelContext* context) { const size_t src_index = 0; // index of src input tensor @@ -1081,7 +1084,7 @@ class MklReluGradOp : public MklReluGradOpBase { ~MklReluGradOp() {} explicit MklReluGradOp(OpKernelConstruction* context) - : MklReluGradOpBase(context) {} + : MklReluGradOpBase(context, 0.0f, 0.0f) {} virtual void Compute_Scalar(OpKernelContext* context) { const size_t diff_dst_index = 0; // index of diff_dst input tensor @@ -1115,7 +1118,7 @@ class MklEluOp : public MklReluOpBase { ~MklEluOp() {} explicit MklEluOp(OpKernelConstruction* context) - : MklReluOpBase(context) {} + : MklReluOpBase(context, 0.0f, 0.0f) {} virtual void Compute_Scalar(OpKernelContext* context) { const size_t src_index = 0; // index of src input tensor @@ -1148,7 +1151,7 @@ class MklEluGradOp : public MklReluGradOpBase { ~MklEluGradOp() {} explicit MklEluGradOp(OpKernelConstruction* context) - : MklReluGradOpBase(context) {} + : MklReluGradOpBase(context, 0.0f, 0.0f) {} virtual void Compute_Scalar(OpKernelContext* context) { const size_t diff_dst_index = 0; // index of diff_dst input tensor @@ -1187,7 +1190,7 @@ class MklTanhOp : public MklReluOpBase { ~MklTanhOp() {} explicit MklTanhOp(OpKernelConstruction* context) - : MklReluOpBase(context) {} + : MklReluOpBase(context, 0.0f, 0.0f) {} virtual void Compute_Scalar(OpKernelContext* context) { const size_t src_index = 0; // index of src input tensor @@ -1219,7 +1222,7 @@ class MklTanhGradOp : public MklReluGradOpBase { ~MklTanhGradOp() {} explicit MklTanhGradOp(OpKernelConstruction* context) - : MklReluGradOpBase(context) {} + : MklReluGradOpBase(context, 0.0f, 0.0f) {} virtual void Compute_Scalar(OpKernelContext* context) { const size_t diff_dst_index = 0; // index of diff_dst input tensor @@ -1251,6 +1254,76 @@ class MklTanhGradOp : public MklReluGradOpBase { } }; +#define RELU6_UPPER_BOUND 6.0f +template +class MklRelu6Op : public MklReluOpBase { + public: + ~MklRelu6Op() {} + + explicit MklRelu6Op(OpKernelConstruction* context) + : MklReluOpBase( + context, RELU6_UPPER_BOUND, 0.0f) {} + + virtual void Compute_Scalar(OpKernelContext* context) { + const size_t src_index = 0; // index of src input tensor + const size_t dst_index = 0; // index of dst output tensor + const Tensor& src_tensor = MklGetInput(context, src_index); + MklDnnShape dnn_shape_src; + GetMklShape(context, src_index, &dnn_shape_src); + + Tensor* dst_tensor = nullptr; + void* user_i = + static_cast(const_cast(src_tensor.flat().data())); + MklDnnShape dnn_shape_dst; + dnn_shape_dst.SetMklTensor(false); + AllocateOutputSetMklShape(context, dst_index, &dst_tensor, + src_tensor.shape(), dnn_shape_dst); + void* out_o = static_cast(dst_tensor->flat().data()); + (static_cast(out_o))[0] = + std::min(std::max((static_cast(user_i))[0], static_cast(0)), + static_cast(RELU6_UPPER_BOUND)); + return; + } +}; + +template +class MklRelu6GradOp + : public MklReluGradOpBase { + public: + ~MklRelu6GradOp() {} + + explicit MklRelu6GradOp(OpKernelConstruction* context) + : MklReluGradOpBase( + context, RELU6_UPPER_BOUND, 0.0f) {} + + virtual void Compute_Scalar(OpKernelContext* context) { + const size_t diff_dst_index = 0; // index of diff_dst input tensor + const size_t src_index = 1; // index of src input tensor + const size_t diff_src_index = 0; // index of diff_src output tensor + const Tensor& src_tensor = MklGetInput(context, src_index); + const Tensor& diff_dst_tensor = MklGetInput(context, diff_dst_index); + Tensor* diff_src_tensor = nullptr; + + MklDnnShape dnn_shape_diff_dst; + GetMklShape(context, diff_dst_index, &dnn_shape_diff_dst); + + MklDnnShape dnn_shape_diff_src; + dnn_shape_diff_src.SetMklTensor(false); + AllocateOutputSetMklShape(context, diff_src_index, &diff_src_tensor, + diff_dst_tensor.shape(), dnn_shape_diff_src); + void* out_o = static_cast(diff_src_tensor->flat().data()); + void* user_i = + static_cast(const_cast(src_tensor.flat().data())); + void* user_g = + static_cast(const_cast(diff_dst_tensor.flat().data())); + (static_cast(out_o))[0] = + (static_cast(user_g))[0] * + ((static_cast(user_i))[0] > 0 && + (static_cast(user_i))[0] < static_cast(RELU6_UPPER_BOUND)); + return; + } +}; + #endif // register dnn kernels for supported operations and supported types @@ -1296,6 +1369,19 @@ TF_CALL_float(REGISTER_ELU_MKL_SUPPORTED_KERNELS_TYPES); MklTanhGradOp); TF_CALL_float(REGISTER_TANH_MKL_SUPPORTED_KERNELS_TYPES); +#define REGISTER_RELU6_MKL_SUPPORTED_KERNELS_TYPES(type) \ + REGISTER_KERNEL_BUILDER(Name("_MklRelu6") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklRelu6Op); \ + REGISTER_KERNEL_BUILDER(Name("_MklRelu6Grad") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklRelu6GradOp); +TF_CALL_float(REGISTER_RELU6_MKL_SUPPORTED_KERNELS_TYPES); + #endif } // namespace tensorflow diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 2485fa4717..8668d69a4d 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -1850,6 +1850,37 @@ NOTE Do not invoke this operator directly in Python. Graph rewrite pass is expected to invoke these operators. )doc"); +REGISTER_OP("_MklRelu6") + .Input("features: T") + .Input("mkl_features: uint8") + .Output("activations: T") + .Output("mkl_activations: uint8") + .Attr("T: realnumbertype") + .SetShapeFn(shape_inference::UnchangedShape) + .Doc(R"doc( +MKL version of Relu6 operator. Uses MKL DNN APIs to implement Relu6 operator. + +NOTE Do not invoke this operator directly in Python. Graph rewrite pass is +expected to invoke these operators. +)doc"); + +REGISTER_OP("_MklRelu6Grad") + .Input("gradients: T") + .Input("features: T") + .Input("mkl_gradients: uint8") + .Input("mkl_features: uint8") + .Output("backprops: T") + .Output("mkl_backprops: uint8") + .Attr("T: realnumbertype") + .SetShapeFn(shape_inference::MergeBothInputsShapeFn) + .Doc(R"doc( +MKL version of Relu6Grad operator. Uses MKL DNN APIs to compute rectified +linear gradients for Relu6 operation. + +NOTE Do not invoke this operator directly in Python. Graph rewrite pass is +expected to invoke these operators. +)doc"); + REGISTER_OP("_MklElu") .Input("features: T") .Input("mkl_features: uint8") -- GitLab From 172d199db9bab808723d24ea586322f1b2d80413 Mon Sep 17 00:00:00 2001 From: leondgarse Date: Thu, 13 Sep 2018 16:12:18 +0800 Subject: [PATCH 0017/1825] Use the last char instead of the first in prediction --- .../examples/generative_examples/text_generation.ipynb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb b/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb index e0d5e494d4..07dbfd3630 100644 --- a/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb +++ b/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb @@ -598,19 +598,13 @@ "# empty string to store our results\n", "text_generated = ''\n", "\n", - "# low temperatures results in more predictable text.\n", - "# higher temperatures results in more surprising text\n", - "# experiment to find the best setting\n", - "temperature = 1.0\n", - "\n", "# hidden state shape == (batch_size, number of rnn units); here batch size == 1\n", "hidden = [tf.zeros((1, units))]\n", "for i in range(num_generate):\n", " predictions, hidden = model(input_eval, hidden)\n", "\n", " # using a multinomial distribution to predict the word returned by the model\n", - " predictions = predictions / temperature\n", - " predicted_id = tf.argmax(predictions[0]).numpy()\n", + " predicted_id = tf.argmax(predictions[-1]).numpy()\n", " \n", " # We pass the predicted word as the next input to the model\n", " # along with the previous hidden state\n", -- GitLab From 11e6c7532899a078d20f1f19441fb7bcfadc93c0 Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Thu, 13 Sep 2018 07:30:05 -0700 Subject: [PATCH 0018/1825] Limit vectorized methods to linux platforms (for now). Remove commented out code --- .../core/kernels/crop_resize_bilinear_core.h | 73 +++++-------------- tensorflow/core/kernels/resize_bilinear_op.cc | 8 +- 2 files changed, 24 insertions(+), 57 deletions(-) diff --git a/tensorflow/core/kernels/crop_resize_bilinear_core.h b/tensorflow/core/kernels/crop_resize_bilinear_core.h index 0209130b2c..3125fbdd3d 100644 --- a/tensorflow/core/kernels/crop_resize_bilinear_core.h +++ b/tensorflow/core/kernels/crop_resize_bilinear_core.h @@ -478,7 +478,21 @@ void crop_resize_single_image(const T* image, const int64 in_height, } } -#ifdef __SSE4_1__ +// template for method that calls either explicitly vectorized method +// or the fallback method, depending on what is appropriate for the +// machine you are running on +template +void crop_resize_single_image_common( + const T* image, const int64 in_height, const int64 in_width, + const int64 out_height, const int64 out_width, const int channels, + const int min_ix, const int max_ix, const CachedInterpolation* xs, + const int min_iy, const int max_iy, const CachedInterpolation* ys, + const float extrapolated_value, const bool flip_x, const bool flip_y, + U* output) TF_ATTRIBUTE_NOINLINE; + +// For now, only compile vectorized code on LINUX systems. +// to-do: Test vectorized code on other platforms (MacOS and Windows). +#if defined(__linux__) && defined(__SSE4_1__) // // The remaining code implements explicitly vectorized versions of a bilinear @@ -3605,6 +3619,7 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { // copy xs values, but filter out the following: // xs[].lower == xs[].upper AND xs[].lerp == 0 // xs[].lower == xs[].upper AND xs[].lerp == 1 + assert( min_ix_ <= max_ix_ ); xs_ = new CachedInterpolation[max_ix_ - min_ix_ + 1]; for (int i = min_ix_; i <= max_ix_; ++i) { int ix = i - min_ix_; @@ -3731,11 +3746,6 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { bool Load4_ok_(const int min_xidx, const int max_xidx); bool Load8_ok_(const int min_xidx, const int max_xidx); - // debugging - int y_; - const T* input_image_; - U* output_image_; - public: // // public client methods @@ -3751,9 +3761,6 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { template void CropResizeCastImage::Resize(const T* input_image, U* output_image) { - // store these for debugging - input_image_ = input_image; - output_image_ = output_image_; // U uEx = cast_to(extrapolated_value_, _f_min_val, _f_max_val, _u_min_val, _u_max_val); @@ -3798,7 +3805,6 @@ void CropResizeCastImage::Resize(const T* input_image, U* output_image) { // interpolation region int y = y0_; for (y = y0_; y + 1 <= y1_; y += 2) { - y_ = y; const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; const float yA_lerp = ys_[iyA].lerp; const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); @@ -3903,11 +3909,8 @@ void CropResizeCastImage::Resize(const T* input_image, U* output_image) { } else { assert(false); } - // printf("*2 :: y=%d, channels_=%d, - // num_load8_=%d\n",y,channels_,num_load8_); } for (; y <= y1_; ++y) { - y_ = y; const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; const float yA_lerp = ys_[iyA].lerp; const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); @@ -3963,7 +3966,6 @@ void CropResizeCastImage::Resize(const T* input_image, U* output_image) { } else { assert(false); } - // printf("*1 :: y=%d\n",y); } } @@ -5025,7 +5027,6 @@ void CropResizeCastImage::Configure_() { assert(load_group >= 0 && load_group <= 4); int current = num_cases[load_group]; assert(current >= 0); - // printf(" ... load_group=%d, current=%d\n",load_group,current); if (load_group == 0) { // general case assert(current < num_general_); @@ -5036,7 +5037,6 @@ void CropResizeCastImage::Configure_() { load1_x_[current] = x; int min_xidx, max_xidx; ComputeXIndexRange_(x, &min_xidx, &max_xidx); - // printf(" ... x=%d, min_xidx=%d, max_xidx=%d\n",x,min_xidx,max_xidx); load1_offsets_[current] = min_xidx * channels_; float* xs_lerp = (float*)(load1_shuffle_masks_ + current * channels_ * 3); char* shufmasks1 = @@ -5049,15 +5049,12 @@ void CropResizeCastImage::Configure_() { float lerp = xs_[ix].lerp; int widx0 = xs_[ix].lower - load1_offsets_[current]; // word index within SSE vector - // printf(" ..... pix_ix=%d, lerp=%f, widx0=%d\n",ix,lerp,widx0); for (int ch = 0; ch < channels_; ++ch) { int idx = pix * channels_ + ch; xs_lerp[idx] = lerp; int shufvec = idx / 4; int shufidx = idx % 4; int widx = widx0 + ch; - // printf(" ....... ch=%d, idx=%d, shufvec=%d, shufidx=%d, widx=%d, - // shufmasks1[%ld...]=...\n",ch,idx,shufvec,shufidx,widx,shufvec*16+shufidx*sizeof(T)); for (int b = 0; b < sizeof(T); ++b) { shufmasks1[shufvec * 16 + shufidx * sizeof(T) + b] = widx * sizeof(T) + b; @@ -5111,12 +5108,6 @@ void CropResizeCastImage::Configure_() { xs_lerp[idx] = lerp; } } - /* debug - printf("load4from4_%dch :: x=%d - - index={%ld",channels_,x,index[0]*sizeof(T)); - for (int i = 1; i < 4; ++i) printf(",%ld",index[i]*sizeof(T)); - printf("}\n"); - */ } else if (load_group == 4) { // load4from8 assert(current < num_load8_); @@ -5133,20 +5124,6 @@ void CropResizeCastImage::Configure_() { xs_lerp[idx] = lerp; } } - /* debug - printf("x=%d :: load8_x_[%d] = %d",x,current,load8_x_[current]); - printf(", load8_offsets_[%d] = {%d",current*4,load8_offsets_[current*4]); - for (int pix = 1; pix < 4; ++pix) - printf(",%d",load8_offsets_[current*4+pix]); - printf("}"); - for (int ch = 0; ch < channels_; ++ch) { - float* p = (float*)(load8_mmxs_lerp_ + current * channels_ + ch); - printf(", lerp[%d] = {%.3f",current*channels_+ch,p[0]); - for (int j = 1; j < 4; ++j) printf(",%.3f",p[j]); - printf("}"); - } - printf("\n"); - */ } else { assert(false); } @@ -5517,19 +5494,6 @@ bool CropResizeCastImage::clip_necessary() { return false; } -#endif // __SSE4_1__ - -template -void crop_resize_single_image_common( - const T* image, const int64 in_height, const int64 in_width, - const int64 out_height, const int64 out_width, const int channels, - const int min_ix, const int max_ix, const CachedInterpolation* xs, - const int min_iy, const int max_iy, const CachedInterpolation* ys, - const float extrapolated_value, const bool flip_x, const bool flip_y, - U* output) TF_ATTRIBUTE_NOINLINE; - -#ifdef __SSE4_1__ - // full specializations of crop_resize_single_image_common for data types that // have vectorized implementations. // at the moment, this is uint8, int8, uint16, int16, int32, Eigen::half, @@ -5594,8 +5558,9 @@ CROP_RESIZE_SINGLE_IMAGE_REGULAR(double, float) #else -// the vectorized implementations need at least SSE4.1 to compile. -// if that is not enabled, default to original code. +// compile fall-back code if either +// a) target is not a linux machine +// b) target architecture does not support at least SSE4.1 template void crop_resize_single_image_common( diff --git a/tensorflow/core/kernels/resize_bilinear_op.cc b/tensorflow/core/kernels/resize_bilinear_op.cc index 5cc8799346..566e94cdef 100644 --- a/tensorflow/core/kernels/resize_bilinear_op.cc +++ b/tensorflow/core/kernels/resize_bilinear_op.cc @@ -90,12 +90,13 @@ struct ResizeBilinear { return; } - std::vector ys(out_height + 1); - std::vector xs(out_width + 1); - // Compute the cached interpolation weights on the x and y dimensions. + std::vector ys; + ys.resize(out_height + 1); compute_interpolation_weights(out_height, in_height, height_scale, ys.data()); + std::vector xs; + xs.resize(out_width + 1); compute_interpolation_weights(out_width, in_width, width_scale, xs.data()); // Scale x interpolation weights to avoid a multiplication during iteration. @@ -111,6 +112,7 @@ struct ResizeBilinear { out_height - 1, ys.data(), 0.0f, false, false, output.data() + (int64)b * out_batch_num_values); } + // xs and ys are freed when they go out of scope } }; } // namespace functor -- GitLab From 848a72432d2093893bc3a70ed41d528876ffa324 Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Thu, 13 Sep 2018 11:24:21 -0700 Subject: [PATCH 0019/1825] Run clang-format again. Don't override compile target switch(es). --- tensorflow/core/kernels/BUILD | 4 ++-- tensorflow/core/kernels/crop_resize_bilinear_core.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 98e1f0ab13..08f698c257 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -2156,7 +2156,7 @@ tf_kernel_library( tf_kernel_library( name = "crop_and_resize_op", - copts = tf_copts() + if_linux_x86_64(["-msse4.1 -finline-functions"]), + copts = tf_copts() + if_linux_x86_64(["-finline-functions"]), prefix = "crop_and_resize_op", deps = IMAGE_DEPS + [":crop_resize_bilinear_core"], ) @@ -2223,7 +2223,7 @@ tf_kernel_library( tf_kernel_library( name = "resize_bilinear_op", - copts = tf_copts() + if_linux_x86_64(["-msse4.1 -finline-functions"]), + copts = tf_copts() + if_linux_x86_64(["-finline-functions"]), prefix = "resize_bilinear_op", deps = IMAGE_DEPS + [":crop_resize_bilinear_core"], ) diff --git a/tensorflow/core/kernels/crop_resize_bilinear_core.h b/tensorflow/core/kernels/crop_resize_bilinear_core.h index 3125fbdd3d..62c275d4cc 100644 --- a/tensorflow/core/kernels/crop_resize_bilinear_core.h +++ b/tensorflow/core/kernels/crop_resize_bilinear_core.h @@ -3619,7 +3619,7 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { // copy xs values, but filter out the following: // xs[].lower == xs[].upper AND xs[].lerp == 0 // xs[].lower == xs[].upper AND xs[].lerp == 1 - assert( min_ix_ <= max_ix_ ); + assert(min_ix_ <= max_ix_); xs_ = new CachedInterpolation[max_ix_ - min_ix_ + 1]; for (int i = min_ix_; i <= max_ix_; ++i) { int ix = i - min_ix_; -- GitLab From 1557c36e6552138ba3aacb8a56fcd082c76ed606 Mon Sep 17 00:00:00 2001 From: Rasmi Elasmar Date: Thu, 13 Sep 2018 16:45:31 -0400 Subject: [PATCH 0020/1825] Updated docs to point to tfp instead of tf.contrib --- tensorflow/contrib/bayesflow/__init__.py | 2 ++ tensorflow/contrib/bayesflow/python/ops/monte_carlo.py | 5 ++++- tensorflow/contrib/distributions/__init__.py | 2 ++ .../contrib/distributions/python/ops/bijectors/__init__.py | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/bayesflow/__init__.py b/tensorflow/contrib/bayesflow/__init__.py index 41a8c920fc..493046b399 100644 --- a/tensorflow/contrib/bayesflow/__init__.py +++ b/tensorflow/contrib/bayesflow/__init__.py @@ -14,6 +14,8 @@ # ============================================================================== """Ops for representing Bayesian computation. +Use [tfp](/probability/api_docs/python/tfp) instead. + ## This package provides classes for Bayesian computation with TensorFlow. """ from __future__ import absolute_import diff --git a/tensorflow/contrib/bayesflow/python/ops/monte_carlo.py b/tensorflow/contrib/bayesflow/python/ops/monte_carlo.py index 68fa415eea..28a829d87d 100644 --- a/tensorflow/contrib/bayesflow/python/ops/monte_carlo.py +++ b/tensorflow/contrib/bayesflow/python/ops/monte_carlo.py @@ -12,7 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Monte Carlo integration and helpers.""" +"""Monte Carlo integration and helpers. + +Use [tfp.monte_carlo](/probability/api_docs/python/tfp/monte_carlo) instead. +""" from __future__ import absolute_import from __future__ import division diff --git a/tensorflow/contrib/distributions/__init__.py b/tensorflow/contrib/distributions/__init__.py index 5cec93c4df..92bb058e17 100644 --- a/tensorflow/contrib/distributions/__init__.py +++ b/tensorflow/contrib/distributions/__init__.py @@ -13,6 +13,8 @@ # limitations under the License. # ============================================================================== """Classes representing statistical distributions and ops for working with them. + +Use [tfp.distributions](/probability/api_docs/python/tfp/distributions) instead. """ from __future__ import absolute_import from __future__ import division diff --git a/tensorflow/contrib/distributions/python/ops/bijectors/__init__.py b/tensorflow/contrib/distributions/python/ops/bijectors/__init__.py index e141f8b5c6..3b17de9b8a 100644 --- a/tensorflow/contrib/distributions/python/ops/bijectors/__init__.py +++ b/tensorflow/contrib/distributions/python/ops/bijectors/__init__.py @@ -14,6 +14,8 @@ # ============================================================================== """Bijector Ops. +Use [tfp.bijectors](/probability/api_docs/python/tfp/bijectors) instead. + @@AbsoluteValue @@Affine @@AffineLinearOperator -- GitLab From c81edb2c1a1eb8b6c831978aed5cb1d3b89f14af Mon Sep 17 00:00:00 2001 From: Mahmoud Abuzaina Date: Thu, 13 Sep 2018 13:50:57 -0700 Subject: [PATCH 0021/1825] Fixed more merge conflicts --- tensorflow/core/BUILD | 7 +- tensorflow/core/graph/mkl_graph_util.h | 29 +- tensorflow/core/kernels/BUILD | 11 +- tensorflow/core/kernels/mkl_conv_ops.cc | 859 ++++++++++++++++-- .../core/kernels/mkl_quantized_conv_ops.h | 55 ++ tensorflow/core/ops/mkl_nn_ops.cc | 612 +++++++++++++ tensorflow/core/util/mkl_util.h | 16 + 7 files changed, 1503 insertions(+), 86 deletions(-) create mode 100644 tensorflow/core/kernels/mkl_quantized_conv_ops.h create mode 100644 tensorflow/core/ops/mkl_nn_ops.cc diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 8f32bc2844..df6b2297b4 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -1051,6 +1051,7 @@ tf_gen_op_libs( "logging_ops", "manip_ops", "math_ops", + "mkl_nn_ops", "nn_ops", "no_op", "parsing_ops", @@ -1189,7 +1190,7 @@ cc_library( ":training_ops_op_lib", ":user_ops_op_lib", ":word2vec_ops", - ] + tf_additional_cloud_op_deps(), + ] + if_mkl([":mkl_nn_ops_op_lib"]) + tf_additional_cloud_op_deps(), alwayslink = 1, ) @@ -1244,7 +1245,9 @@ cc_library( ":framework", ":lib", ":nn_ops_op_lib", - ], + ] + if_mkl([ + ":mkl_nn_ops_op_lib", + ]), alwayslink = 1, ) diff --git a/tensorflow/core/graph/mkl_graph_util.h b/tensorflow/core/graph/mkl_graph_util.h index bab1df87a4..990b2fe9b0 100644 --- a/tensorflow/core/graph/mkl_graph_util.h +++ b/tensorflow/core/graph/mkl_graph_util.h @@ -75,6 +75,8 @@ int inline GetTensorMetaDataIndex(int n, int total_tensors) { namespace mkl_op_registry { static const char* kMklOpLabel = "MklOp"; static const char* kMklOpLabelPattern = "label='MklOp'"; +static const char* kMklQuantizedOpLabel = "QuantizedMklOp"; +static const char* kMklQuantizedOpLabelPattern = "label='QuantizedMklOp'"; // Prefix that we add to Tensorflow op name to construct Mkl op name. static const char* const kMklOpPrefix = "_Mkl"; @@ -91,9 +93,30 @@ inline string GetMklOpName(const string& name) { // @return: true if opname is registered as Mkl op; false otherwise static inline bool IsMklOp(const string& op_name, DataType T) { string kernel = KernelsRegisteredForOp(op_name); - bool result = - kernel.find(kMklOpLabelPattern) != string::npos && (T == DT_FLOAT); - return result; + + // Restrict quantized ops to QUINT8 and QINT8 for now + if (kernel.find(kMklQuantizedOpLabelPattern) != string::npos) { + return (T == DT_QUINT8 || T == DT_QINT8); + } + // Restrict regular ops to FLOAT + if (kernel.find(kMklOpLabelPattern) != string::npos) { + return (T == DT_FLOAT); + } + return false; +} + +// TODO(mdfaijul): QuantizedConv2D is registered with input: QUINT8 +// filter:QINT8 for mkldnn integration. First a dummy kernel is created +// and then it is replaced by an actual kernel. +static inline bool IsMklOp(const string& op_name, DataType Tinput, + DataType Tfilter) { + string kernel = KernelsRegisteredForOp(op_name); + + // Restrict quantized ops to QUINT8 and QINT8 for now + if (kernel.find(kMklQuantizedOpLabelPattern) != string::npos) { + return (Tinput == DT_QUINT8 && Tfilter == DT_QINT8); + } + return false; } // Check whether opname with type T is registered as MKL-compliant and diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 94d3ab4467..d1e1596b0b 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -3468,7 +3468,7 @@ NN_DEPS = [ "//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", @@ -6215,6 +6215,7 @@ tf_cc_test( tf_mkl_kernel_library( name = "mkl_conv_op", + hdrs = ["mkl_quantized_conv_ops.h"], prefix = "mkl_conv", deps = [ ":bounds_check", @@ -6224,6 +6225,7 @@ 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(), ) @@ -6238,6 +6240,7 @@ 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(), ) @@ -6253,6 +6256,7 @@ 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(), ) @@ -6272,6 +6276,7 @@ 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(), ) @@ -6286,6 +6291,7 @@ 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(), @@ -6301,6 +6307,7 @@ 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(), @@ -6321,7 +6328,7 @@ tf_mkl_kernel_library( tf_mkl_kernel_library( name = "mkl_concat_op", prefix = "mkl_concat_op", - deps = ARRAY_DEPS + mkl_deps(), + deps = [":quantization_utils"] + ARRAY_DEPS + mkl_deps(), ) tf_mkl_kernel_library( diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index 184e0cb003..95b6dc066c 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -17,9 +17,9 @@ limitations under the License. #ifdef INTEL_MKL #include +#include #include #include -#include #include "tensorflow/core/framework/numeric_op.h" #include "tensorflow/core/framework/op_kernel.h" @@ -29,6 +29,8 @@ limitations under the License. #include "tensorflow/core/framework/tensor_slice.h" #include "tensorflow/core/kernels/bounds_check.h" #include "tensorflow/core/kernels/mkl_conv_ops.h" +#include "tensorflow/core/kernels/mkl_quantized_conv_ops.h" +#include "tensorflow/core/kernels/no_op.h" #include "tensorflow/core/kernels/ops_util.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/gtl/array_slice.h" @@ -69,6 +71,12 @@ struct MklConvFwdParams { memory::dims dilations; memory::dims padding_left; memory::dims padding_right; + string dtypes = string(""); + struct PostOpParam { + string name; + std::vector param; + }; + std::vector post_op_params; MklConvFwdParams(memory::dims src_dims, memory::dims filter_dims, memory::dims bias_dims, memory::dims dst_dims, @@ -83,8 +91,10 @@ struct MklConvFwdParams { padding_left(padding_left), padding_right(padding_right) {} }; - -template +// With quantization, input, filter, and output can have different types +// so we use differnt template parameter for each type +template class MklConvFwdPrimitive : public MklPrimitive { public: explicit MklConvFwdPrimitive(const MklConvFwdParams& convFwdDims) @@ -103,16 +113,16 @@ class MklConvFwdPrimitive : public MklPrimitive { // filter_data: input data buffer of filter (weights) // bias_data: input data buffer of bias // dst_data: output data buffer of dst - void Execute(const T* src_data, const T* filter_data, const T* bias_data, - const T* dst_data) { + void Execute(const Tinput* src_data, const Tfilter* filter_data, + const Tbias* bias_data, const Toutput* dst_data) { context_.src_mem->set_data_handle( - static_cast(const_cast(src_data))); + static_cast(const_cast(src_data))); context_.filter_mem->set_data_handle( - static_cast(const_cast(filter_data))); + static_cast(const_cast(filter_data))); context_.bias_mem->set_data_handle( - static_cast(const_cast(bias_data))); + static_cast(const_cast(bias_data))); context_.dst_mem->set_data_handle( - static_cast(const_cast(dst_data))); + static_cast(const_cast(dst_data))); context_.fwd_stream->submit(context_.fwd_primitives); // after exec, set data handle back @@ -128,13 +138,14 @@ class MklConvFwdPrimitive : public MklPrimitive { // src_data: input data buffer of src // filter_data: input data buffer of filter (weights) // dst_data: output data buffer of dst - void Execute(const T* src_data, const T* filter_data, const T* dst_data) { + void Execute(const Tinput* src_data, const Tfilter* filter_data, + const Toutput* dst_data) { context_.src_mem->set_data_handle( - static_cast(const_cast(src_data))); + static_cast(const_cast(src_data))); context_.filter_mem->set_data_handle( - static_cast(const_cast(filter_data))); + static_cast(const_cast(filter_data))); context_.dst_mem->set_data_handle( - static_cast(const_cast(dst_data))); + static_cast(const_cast(dst_data))); context_.fwd_stream->submit(context_.fwd_primitives); // after execution, set data handle back @@ -200,17 +211,17 @@ class MklConvFwdPrimitive : public MklPrimitive { void Setup(const MklConvFwdParams& convFwdDims) { // create memory descriptors for convolution data w/ no specified format context_.src_md.reset(new memory::desc( - {convFwdDims.src_dims}, MklDnnType(), memory::format::any)); + {convFwdDims.src_dims}, MklDnnType(), memory::format::any)); context_.filter_md.reset(new memory::desc( - {convFwdDims.filter_dims}, MklDnnType(), memory::format::any)); + {convFwdDims.filter_dims}, MklDnnType(), memory::format::any)); context_.dst_md.reset(new memory::desc( - {convFwdDims.dst_dims}, MklDnnType(), memory::format::any)); + {convFwdDims.dst_dims}, MklDnnType(), memory::format::any)); if (!convFwdDims.bias_dims.empty()) context_.bias_md.reset(new memory::desc( - {convFwdDims.bias_dims}, MklDnnType(), memory::format::any)); + {convFwdDims.bias_dims}, MklDnnType(), memory::format::any)); // create a convolution if (!convFwdDims.bias_dims.empty()) { @@ -230,6 +241,42 @@ class MklConvFwdPrimitive : public MklPrimitive { context_.fwd_pd.reset(new convolution_forward::primitive_desc( *context_.fwd_desc, cpu_engine_)); + // Check if there is any fusions as post-ops + auto const& post_op_params = convFwdDims.post_op_params; + mkldnn::primitive_attr post_ops_attr; + mkldnn::post_ops post_ops; + if (!post_op_params.empty()) { + for (auto const& post_op_param : post_op_params) { + if (post_op_param.name == "relu") { + CHECK_EQ(post_op_param.param.size(), 3); + float op_scale = post_op_param.param[0]; + float op_alpha = post_op_param.param[1]; + float op_beta = post_op_param.param[2]; + post_ops.append_eltwise(op_scale, mkldnn::eltwise_relu, op_alpha, + op_beta); + } else if (post_op_param.name == "sum") { + CHECK_EQ(post_op_param.param.size(), 1); + float op_scale = post_op_param.param[0]; + post_ops.append_sum(op_scale); + } else if (post_op_param.name == "output_scale") { + CHECK_EQ(post_op_param.param.size(), 1); + std::vector scales; + scales.push_back(post_op_param.param[0]); + post_ops_attr.set_output_scales(0, scales); + } else { + TF_CHECK_OK( + Status(error::Code::UNIMPLEMENTED, + "For now, only Relu and Sum are supported for fusion.")); + } + } + 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_)); + } else { + context_.fwd_pd.reset(new convolution_forward::primitive_desc( + *context_.fwd_desc, cpu_engine_)); + } + // store the expected memory format context_.src_fmt = static_cast( context_.fwd_pd.get()->src_primitive_desc().desc().data.format); @@ -268,23 +315,30 @@ class MklConvFwdPrimitive : public MklPrimitive { engine cpu_engine_; }; -template +template class MklConvFwdPrimitiveFactory : public MklPrimitiveFactory { public: - static MklConvFwdPrimitive* Get(const MklConvFwdParams& convFwdDims, - bool do_not_cache) { - MklConvFwdPrimitive* conv_fwd = nullptr; + static MklConvFwdPrimitive* Get( + const MklConvFwdParams& convFwdDims, bool do_not_cache) { + MklConvFwdPrimitive* conv_fwd = nullptr; if (do_not_cache) { /* Always create new primitive */ - conv_fwd = new MklConvFwdPrimitive(convFwdDims); + conv_fwd = new MklConvFwdPrimitive( + convFwdDims); } else { // try to find a suitable one in pool - conv_fwd = dynamic_cast*>( - MklConvFwdPrimitiveFactory::GetInstance().GetConvFwd(convFwdDims)); + conv_fwd = + dynamic_cast*>( + MklConvFwdPrimitiveFactory::GetInstance() + .GetConvFwd(convFwdDims)); if (conv_fwd == nullptr) { - conv_fwd = new MklConvFwdPrimitive(convFwdDims); - MklConvFwdPrimitiveFactory::GetInstance().SetConvFwd(convFwdDims, - conv_fwd); + conv_fwd = new MklConvFwdPrimitive( + convFwdDims); + MklConvFwdPrimitiveFactory::GetInstance() + .SetConvFwd(convFwdDims, conv_fwd); } } @@ -314,6 +368,31 @@ class MklConvFwdPrimitiveFactory : public MklPrimitiveFactory { key_creator.AddAsKey(convFwdDims.dilations); key_creator.AddAsKey(convFwdDims.padding_left); key_creator.AddAsKey(convFwdDims.padding_right); + key_creator.AddAsKey(convFwdDims.dtypes); + + // Generate keys for post-ops + for (auto const& post_op_param : convFwdDims.post_op_params) { + if (post_op_param.name == "relu") { + CHECK_EQ(post_op_param.param.size(), 3); + key_creator.AddAsKey(post_op_param.name); + key_creator.AddAsKey(post_op_param.param[0]); + key_creator.AddAsKey(post_op_param.param[1]); + key_creator.AddAsKey(post_op_param.param[2]); + } else if (post_op_param.name == "sum") { + CHECK_EQ(post_op_param.param.size(), 1); + key_creator.AddAsKey(post_op_param.name); + key_creator.AddAsKey(post_op_param.param[0]); + } else if (post_op_param.name == "output_scale") { + CHECK_EQ(post_op_param.param.size(), 1); + key_creator.AddAsKey(post_op_param.name); + key_creator.AddAsKey(post_op_param.param[0]); + } else { + TF_CHECK_OK( + Status(error::Code::UNIMPLEMENTED, + "For now, only Relu and Sum are supported for fusion.")); + } + } + return key_creator.GetKey(); } @@ -757,10 +836,23 @@ class MklConvOp : public OpKernel { TensorFormat data_format_; }; +// FP32 kernel registration for INTEL_MKL_ML +REGISTER_KERNEL_BUILDER(Name("_MklConv2D") + .Device(DEVICE_CPU) + .TypeConstraint("T") + .Label(mkl_op_registry::kMklOpLabel), + MklConv2DOp); +REGISTER_KERNEL_BUILDER(Name("_MklConv2DWithBias") + .Device(DEVICE_CPU) + .TypeConstraint("T") + .Label(mkl_op_registry::kMklOpLabel), + MklConv2DOp); + #else // Base class for convolution forward operations -template +template class MklConvOp : public OpKernel { public: ~MklConvOp() {} @@ -831,8 +923,8 @@ class MklConvOp : public OpKernel { errors::InvalidArgument("Filter should not be in " "Mkl Layout")); - MklDnnData src(&cpu_engine); - MklDnnData filter(&cpu_engine); + MklDnnData src(&cpu_engine_); + MklDnnData filter(&cpu_engine_); memory::dims src_dims, filter_dims, padding_left, padding_right, dilations, strides; @@ -865,9 +957,15 @@ class MklConvOp : public OpKernel { // as 2nd output of Conv2D/3D. filter_mkl_shape.SetMklTensor(false); Tensor* output_filter_tensor = nullptr; - AllocateOutputSetMklShape(context, kOutputIndex_Filter, - &output_filter_tensor, - filter_tf_shape, filter_mkl_shape); + // MklConv2D also outputs converted filter as 2nd output. + if (typeid(Tinput) == typeid(float) && + typeid(Tfilter) == typeid(float) && + typeid(Toutput) == typeid(float)) { + filter_mkl_shape.SetMklTensor(false); + AllocateOutputSetMklShape(context, kOutputIndex_Filter, + &output_filter_tensor, filter_tf_shape, + filter_mkl_shape); + } return; } @@ -887,15 +985,17 @@ class MklConvOp : public OpKernel { // Conv3D: NDHWC or NCDHW auto src_md = src_mkl_shape.IsMklTensor() ? src_mkl_shape.GetMklLayout() - : memory::desc(src_dims, MklDnnType(), tf_fmt); + : memory::desc(src_dims, MklDnnType(), tf_fmt); + src.SetUsrMem(src_md, &src_tensor); // Although filter shape (filter_dims) required is in MKL-DNN order, // the layout is Tensorflow's layout (HWIO). auto filter_md = filter_mkl_shape.IsMklTensor() // Should NEVER be true ? filter_mkl_shape.GetMklLayout() - : memory::desc(filter_dims, MklDnnType(), + : memory::desc(filter_dims, MklDnnType(), isConv2D ? memory::format::hwio : memory::format::dhwio); + filter.SetUsrMem(filter_md, &filter_tensor); // MKLDNN dilation starts from 0. for (int i = 0; i < dilations.size(); i++) dilations[i] -= 1; @@ -905,27 +1005,39 @@ class MklConvOp : public OpKernel { // in the following cases // 1. Legacy CPU without AVX512/AVX2, or // 2. 1x1 convolution with stride != 1 - bool do_not_cache = MklPrimitiveFactory::IsPrimitiveMemOptEnabled() && + bool do_not_cache = MklPrimitiveFactory::IsPrimitiveMemOptEnabled() && (src_dims[MklDnnDims::Dim_N] > kSmallBatchSize) && - (MklPrimitiveFactory::IsLegacyPlatform() || + (MklPrimitiveFactory::IsLegacyPlatform() || IsConv1x1StrideNot1(filter_dims, strides)); // get a conv2d fwd from primitive pool - MklConvFwdPrimitive* conv_fwd = nullptr; + MklConvFwdPrimitive* + conv_fwd = nullptr; if (biasEnabled) { 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); - conv_fwd = MklConvFwdPrimitiveFactory::Get( - convFwdDims, do_not_cache); + + // 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); - conv_fwd = MklConvFwdPrimitiveFactory::Get( - convFwdDims, do_not_cache); + + // Extend the basic parameters for data types and fusions + this->ExtendConvFwdParams(context, convFwdDims); + + conv_fwd = MklConvFwdPrimitiveFactory::Get(convFwdDims, + do_not_cache); } // allocate output tensors output_tensor and filter_out_tensor @@ -934,38 +1046,42 @@ class MklConvOp : public OpKernel { AllocateOutputTensor(context, *conv_fwd_pd, dst_dims_mkl_order, tf_fmt, &dst_tensor); Tensor* filter_out_tensor = nullptr; - AllocateFilterOutputTensor(context, *conv_fwd_pd, - TFShapeToMklDnnDims(filter_tf_shape), - &filter_out_tensor); + if (typeid(Tinput) == typeid(float) && typeid(Tfilter) == typeid(float) && + typeid(Toutput) == typeid(float)) { + AllocateFilterOutputTensor(context, *conv_fwd_pd, + TFShapeToMklDnnDims(filter_tf_shape), + &filter_out_tensor); + } - T* dst_data = static_cast(dst_tensor->flat().data()); + Ttemp_output* dst_data = + reinterpret_cast(dst_tensor->flat().data()); // check whether src/filter need reorder - T *src_data = nullptr; + Tinput* src_data = nullptr; if (src_md.data.format != conv_fwd->GetSrcMemoryFormat()) { src.SetUsrMem(src_md, &src_tensor); src.CheckReorderToOpMem(conv_fwd_pd.get()->src_primitive_desc()); - src_data = static_cast(src.GetOpMem().get_data_handle()); + src_data = static_cast(src.GetOpMem().get_data_handle()); } else { - src_data = static_cast(const_cast(src_tensor.flat().data())); + src_data = static_cast( + const_cast(src_tensor.flat().data())); } - T* filter_data = nullptr; + Tfilter* filter_data = nullptr; if (filter_md.data.format != conv_fwd->GetFilterMemoryFormat()) { filter.SetUsrMem(filter_md, &filter_tensor); 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())); + static_cast(const_cast(filter_tensor.flat().data())); } // execute convolution if (biasEnabled) { const Tensor& bias_tensor = MklGetInput(context, kInputIndex_Bias); - T* bias_data = static_cast(const_cast( - bias_tensor.flat().data())); - + Tbias* bias_data = + this->GetBiasHandle(context, conv_fwd_pd, bias_tensor); conv_fwd->Execute(src_data, filter_data, bias_data, dst_data); } else { conv_fwd->Execute(src_data, filter_data, dst_data); @@ -982,18 +1098,31 @@ class MklConvOp : public OpKernel { } } - private: - std::vector strides_; - std::vector dilations_; - Padding padding_; - TensorFormat data_format_; - const int kInputIndex_Src = 0, kInputIndex_Filter = 1, kInputIndex_Bias = 2; - const int kOutputIndex_Dst = 0, kOutputIndex_Filter = 1; - const int kDilationH = 0, kDilationW = 1; - engine cpu_engine = engine(engine::cpu, 0); + protected: + virtual void ExtendConvFwdParams(OpKernelContext* context, + MklConvFwdParams& params) { + // Create a string from data types of input, filter, bias, and output. + params.dtypes.append(typeid(Tinput).name()); + params.dtypes.append(typeid(Tfilter).name()); + params.dtypes.append(typeid(Tbias).name()); + params.dtypes.append(typeid(Toutput).name()); + } + + virtual Tbias* GetBiasHandle( + OpKernelContext* context, + std::shared_ptr& + conv2d_fwd_pd, + const Tensor& bias_tensor) { + if (biasEnabled) { + return static_cast( + const_cast(bias_tensor.flat().data())); + } else { + return nullptr; + } + } // Allocate output tensor. - void AllocateOutputTensor( + virtual void AllocateOutputTensor( OpKernelContext* context, const convolution_forward::primitive_desc& conv_prim_desc, const memory::dims& output_dims_mkl_order, @@ -1001,23 +1130,40 @@ class MklConvOp : public OpKernel { CHECK_NOTNULL(output_tensor); auto dst_pd = conv_prim_desc.dst_primitive_desc(); + auto dst_md = dst_pd.desc(); + if (!std::is_same::value) { + dst_md.data.data_type = + static_cast(MklDnnType()); + dst_pd = memory::primitive_desc(dst_md, cpu_engine_); + } // Allocate shape of Mkl tensor. MklDnnShape output_mkl_shape; output_mkl_shape.SetMklTensor(true); output_mkl_shape.SetMklLayout(&dst_pd); - output_mkl_shape.SetElemType(MklDnnType()); + output_mkl_shape.SetElemType(MklDnnType()); output_mkl_shape.SetTfLayout(output_dims_mkl_order.size(), output_dims_mkl_order, output_tf_format); // Allocate shape of TF tensor. TensorShape output_tf_shape; - output_tf_shape.AddDim((dst_pd.get_size() / sizeof(T))); + output_tf_shape.AddDim((dst_pd.get_size() / sizeof(Toutput))); AllocateOutputSetMklShape(context, kOutputIndex_Dst, output_tensor, output_tf_shape, output_mkl_shape); } - // Allocate output tensor. + engine cpu_engine_ = engine(engine::cpu, 0); + + private: + std::vector strides_; + std::vector dilations_; + Padding padding_; + TensorFormat data_format_; + const int kInputIndex_Src = 0, kInputIndex_Filter = 1, kInputIndex_Bias = 2; + 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, @@ -1029,7 +1175,7 @@ class MklConvOp : public OpKernel { MklDnnShape filter_mkl_shape; filter_mkl_shape.SetMklTensor(true); filter_mkl_shape.SetMklLayout(&filter_pd); - filter_mkl_shape.SetElemType(MklDnnType()); + filter_mkl_shape.SetElemType(MklDnnType()); // The format of the filter is actually OIhw8i8o, but TF doesn't support // this format. Just use format::blocked for now because the layout @@ -1039,17 +1185,17 @@ class MklConvOp : public OpKernel { // Allocate the data space for the filter to propagate as TF tensor. TensorShape filter_tf_shape; - filter_tf_shape.AddDim((filter_pd.get_size() / sizeof(T))); + filter_tf_shape.AddDim((filter_pd.get_size() / sizeof(Tfilter))); 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) { + 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 @@ -1080,20 +1226,575 @@ class MklConvOp : public OpKernel { } }; -#endif +// We create new class for each verison of Quantized Convolution and inherit +// from the FP32 version of the base class +template +class MklQuantizedConv2DOp + : public MklConvOp { + public: + virtual ~MklQuantizedConv2DOp() { + if (this->input_bias_ != nullptr) { + delete this->input_bias_; + input_bias_ = nullptr; + } + + if (this->scaled_bias_ != nullptr) { + delete this->scaled_bias_; + scaled_bias_ = nullptr; + } + } + + explicit MklQuantizedConv2DOp(OpKernelConstruction* context) + : MklConvOp(context) {} + + void Compute(OpKernelContext* context) override { + // Compute int32 output tensor + MklConvOp::Compute(context); + + // Compute additional outputs: min/max scalars. + int bias_index_offset; + bias_index_offset = biasEnabled ? 1 : 0; + + const float min_input = + context->input(2 + bias_index_offset).flat()(0); + const float max_input = + context->input(3 + bias_index_offset).flat()(0); + const float min_filter = + context->input(4 + bias_index_offset).flat()(0); + const float max_filter = + context->input(5 + bias_index_offset).flat()(0); + + float min_output_value; + 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 + min_output_value = context->input(6 + bias_index_offset).flat()(0); + max_output_value = context->input(7 + bias_index_offset).flat()(0); + } else { + MklQuantizationRangeForMultiplication( + min_input, max_input, min_filter, max_filter, &min_output_value, + &max_output_value); + } + + Tensor* output_min = nullptr; + Tensor* output_max = nullptr; + MklDnnShape output_min_mkl_shape, output_max_mkl_shape; + output_min_mkl_shape.SetMklTensor(false); + output_max_mkl_shape.SetMklTensor(false); + AllocateOutputSetMklShape(context, 1, &output_min, {}, + output_min_mkl_shape); + AllocateOutputSetMklShape(context, 2, &output_max, {}, + output_max_mkl_shape); + output_min->flat()(0) = min_output_value; + output_max->flat()(0) = max_output_value; + } + + protected: + void ExtendConvFwdParams(OpKernelContext* context, + MklConvFwdParams& params) override { + MklConvOp::ExtendConvFwdParams(context, params); + + // When the output type is quint8, the output data id requantized + // into quint8. A post_op "output_scale" is added to do the conversion. + if (std::is_same::value || + std::is_same::value) { + int bias_index_offset; + bias_index_offset = biasEnabled ? 1 : 0; + + const float min_input = + context->input(2 + bias_index_offset).flat()(0); + const float max_input = + context->input(3 + bias_index_offset).flat()(0); + const float min_filter = + context->input(4 + bias_index_offset).flat()(0); + const float max_filter = + context->input(5 + bias_index_offset).flat()(0); + const float min_freezed_output = + context->input(6 + bias_index_offset).flat()(0); + const float max_freezed_output = + context->input(7 + bias_index_offset).flat()(0); + + float min_output_value; + float max_output_value; + MklQuantizationRangeForMultiplication( + min_input, max_input, min_filter, max_filter, &min_output_value, + &max_output_value); + float scale_int32 = + std::max(std::abs(min_output_value), std::abs(max_output_value)); + float scale_eightbit = + std::max(std::abs(min_freezed_output), std::abs(max_freezed_output)); + float scale = 1.0; + if (std::is_same::value) + scale = scale_int32 / scale_eightbit / static_cast(1 << 23); + else + scale = scale_int32 / scale_eightbit / static_cast(1 << 24); + + std::vector output_scale; + output_scale.push_back(scale); + params.post_op_params.push_back({"output_scale", output_scale}); + } + } + + Tbias* GetBiasHandle( + OpKernelContext* context, + std::shared_ptr& conv_fwd_pd, + const Tensor& bias_tensor) override { + int bias_index_offset; + bias_index_offset = biasEnabled ? 1 : 0; + + const float min_input = + context->input(2 + bias_index_offset).flat()(0); + const float max_input = + context->input(3 + bias_index_offset).flat()(0); + const float min_filter = + context->input(4 + bias_index_offset).flat()(0); + const float max_filter = + context->input(5 + bias_index_offset).flat()(0); + + std::vector net; + if (biasEnabled) { + if (std::is_same::value) { + return static_cast( + const_cast(bias_tensor.flat().data())); + } + // If bias is enabled and requantization is not fused, scale the + // bias to be consistent with quantized-input and quantized-filter. + float bias_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(bias_scale); + mkldnn::primitive_attr bias_attr; + bias_attr.set_output_scales(0, scales); + + void* bias_buf = static_cast( + const_cast(bias_tensor.flat().data())); + input_bias_ = new memory(conv_fwd_pd->bias_primitive_desc(), bias_buf); + scaled_bias_ = new memory(conv_fwd_pd->bias_primitive_desc()); + auto reorder_desc = mkldnn::reorder::primitive_desc( + input_bias_->get_primitive_desc(), scaled_bias_->get_primitive_desc(), + bias_attr); + net.push_back(mkldnn::reorder(reorder_desc, *input_bias_, *scaled_bias_)); + stream(stream::kind::eager).submit(net).wait(); + return reinterpret_cast(scaled_bias_->get_data_handle()); + } else { + return nullptr; + } + } + + memory* input_bias_ = nullptr; + memory* scaled_bias_ = nullptr; +}; + +template +class MklQuantizedConv2DReluOp + : public MklQuantizedConv2DOp { + public: + virtual ~MklQuantizedConv2DReluOp() {} + + explicit MklQuantizedConv2DReluOp(OpKernelConstruction* context) + : MklQuantizedConv2DOp( + context) {} + + protected: + void ExtendConvFwdParams(OpKernelContext* context, + MklConvFwdParams& params) override { + MklQuantizedConv2DOp::ExtendConvFwdParams(context, params); + params.post_op_params.push_back({"relu", {1.0, 0.0, 0.0}}); + } +}; + +template +class MklQuantizedConv2DSumReluOp + : public MklQuantizedConv2DOp { + public: + virtual ~MklQuantizedConv2DSumReluOp() { + if (this->summand_ != nullptr) { + delete this->summand_; + summand_ = nullptr; + } + + if (this->dst_ != nullptr) { + delete this->dst_; + dst_ = nullptr; + } + } + + explicit MklQuantizedConv2DSumReluOp(OpKernelConstruction* context) + : MklQuantizedConv2DOp( + context) {} + + protected: + void ExtendConvFwdParams(OpKernelContext* context, + MklConvFwdParams& params) override { + MklQuantizedConv2DOp::ExtendConvFwdParams(context, params); + // Calculate the scale (beta in mkldnn api term) for sum + if (std::is_same::value) { + int summand_idx = context->num_inputs() / 2 - 1 - 2; + DataType summand_type = this->input_type(summand_idx); + bool summand_condition = + (summand_type == DT_QINT8) || (summand_type == DT_QUINT8); + CHECK((summand_condition)); + int bias_index_offset = biasEnabled ? 1 : 0; + const float min_freezed_output = + context->input(6 + bias_index_offset).flat()(0); + const float max_freezed_output = + context->input(7 + bias_index_offset).flat()(0); + const float min_freezed_summand = + context->input(9 + bias_index_offset).flat()(0); + const float max_freezed_summand = + context->input(10 + bias_index_offset).flat()(0); + + float scale_output = + std::max(std::abs(min_freezed_output), std::abs(max_freezed_output)); + float scale_summand = std::max(std::abs(min_freezed_summand), + std::abs(max_freezed_summand)); + if (summand_type == DT_QUINT8) + params.post_op_params.push_back( + {"sum", {scale_summand / scale_output}}); + else + params.post_op_params.push_back( + {"sum", {2.0 * scale_summand / scale_output}}); + } else { + params.post_op_params.push_back({"sum", {1.0}}); + } + 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 { + int summand_idx = context->num_inputs() / 2 - 1; + float reorder_sum_scale = 1.0; + if (std::is_same::value) { + summand_idx -= 2; + DataType summand_type = this->input_type(summand_idx); + bool summand_condition = + (summand_type == DT_QINT8) || (summand_type == DT_QUINT8); + CHECK((summand_condition)); + Tensor& summand = const_cast(MklGetInput(context, summand_idx)); + MklDnnShape summand_mkl_shape; + GetMklShape(context, summand_idx, &summand_mkl_shape); + auto dst_md = summand_mkl_shape.GetMklLayout(); + if (summand_mkl_shape.IsMklTensor()) { + if (summand_type == DT_QINT8) { + summand.UnsafeCopyFromInternal(summand, DT_QUINT8, summand.shape()); + dst_md.data.data_type = + static_cast(MklDnnType()); + summand_mkl_shape.SetMklLayout(&dst_md); + summand_mkl_shape.SetElemType(MklDnnType()); + } + ForwardMklTensorInToOutWithMklShape(context, summand_idx, 0, + summand_mkl_shape); + *output_tensor = const_cast(&summand); + return; + } else { + TF_CHECK_OK(Status(error::Code::FAILED_PRECONDITION, + "Current fusion is not successful.")); + } + } + // TODO(mdfaijul): Add cleaner code for non-mkl tensor + MklConvOp::AllocateOutputTensor(context, conv_prim_desc, + output_dims_mkl_order, + output_tf_format, + output_tensor); + const Tensor& summand = MklGetInput(context, summand_idx); + if (summand.dtype() != DT_FLOAT) + TF_CHECK_OK(Status(error::Code::FAILED_PRECONDITION, + "Current fusion requires summand to be float")); + MklDnnShape summand_mkl_shape; + GetMklShape(context, summand_idx, &summand_mkl_shape); + // We need to compute scale for the summand + int bias_index_offset = biasEnabled ? 1 : 0; + const float min_input = + context->input(2 + bias_index_offset).flat()(0); + const float max_input = + context->input(3 + bias_index_offset).flat()(0); + const float min_filter = + context->input(4 + bias_index_offset).flat()(0); + 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)) * + std::max(std::abs(max_filter), std::abs(min_filter))); + std::vector scales; + scales.push_back(reorder_sum_scale); + mkldnn::primitive_attr reorder_attr; + reorder_attr.set_output_scales(0, scales); + + auto summand_md = + summand_mkl_shape.IsMklTensor() + ? summand_mkl_shape.GetMklLayout() + : memory::desc(output_dims_mkl_order, MklDnnType(), + memory::format::nhwc); + auto summand_pd = memory::primitive_desc(summand_md, this->cpu_engine_); + void* summand_buf = + static_cast(const_cast(summand.flat().data())); + void* dst_buf = + static_cast((*output_tensor)->flat().data()); + summand_ = new memory(summand_pd, summand_buf); + dst_ = new memory(conv_prim_desc.dst_primitive_desc(), dst_buf); + auto reorder_desc = mkldnn::reorder::primitive_desc( + summand_pd, conv_prim_desc.dst_primitive_desc(), reorder_attr); + + std::vector net; + net.push_back(mkldnn::reorder(reorder_desc, *summand_, *dst_)); + stream(stream::kind::eager).submit(net).wait(); + } + + memory* summand_ = nullptr; + memory* dst_ = nullptr; +}; + +// INT8 kernel registration +// Register NoOp kernel for QunatizedConv2D for qint8 filter +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2D") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +// Register a templatized implementation of MklQuntizedConv2D. +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2D") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DOp); + +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DOp); + +// Register NoOp kernel for QuantizedConv2DWithBias to get a python interface. +// This kernel will be replaced by an MKL kernel during graph +// optimization pass. +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DWithBias") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DWithBiasAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +// Register a templatized implementation MklQuantizedConv2DWithBias. +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBias") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DOp); + +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("Tbias") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DOp); +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("Tbias") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DOp); + +// Register NoOp kernel for QuantizedConv2DAndRelu to get a python interface. +// This kernel will be replaced by an MKL kernel during graph-optimization pass. +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DAndRelu") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +// Register a templatized implementation of MklQuantizedConv2DAndRelu. +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DAndRelu") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DReluOp); + +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DReluOp); + +// Register NoOp kernel for QuantizedConv2DWithBiasAndRelu to get a python +// interface. +// This kernel will be replaced by an MKL kernel during graph-optimization pass. +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DWithBiasAndRelu") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +// Register NoOp kernel for QuantizedConv2DWithBiasAndReluAndRequantize +// to get a python interface. +// This kernel will be replaced by an MKL kernel during graph-optimization pass. +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DWithBiasAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +// Register a templatized implementation of MklQuantizedConv2DWithBiasAndRelu. +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasAndRelu") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DReluOp); + +// Register a templatized implementation of +// MklQuantizedConv2DWithBiasAndReluAndRequantize. +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("Tbias") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DReluOp); +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("Tbias") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DReluOp); + +// Register NoOp kernel for QuantizedConv2DWithBiasSumAndRelu to get a python +// interface. +// This kernel will be replaced by an MKL kernel during graph-optimization pass. +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DWithBiasSumAndRelu") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); + +REGISTER_KERNEL_BUILDER(Name("QuantizedConv2DWithBiasSumAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); +REGISTER_KERNEL_BUILDER( + Name("QuantizedConv2DWithBiasSignedSumAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type"), + NoOp); +// Register a templatized implementation of MklQuantizedConv2DWithBiasAndRelu. +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasSumAndRelu") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DSumReluOp); + +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasSumAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DSumReluOp); +REGISTER_KERNEL_BUILDER( + Name("_MklQuantizedConv2DWithBiasSignedSumAndReluAndRequantize") + .Device(DEVICE_CPU) + .TypeConstraint("Tinput") + .TypeConstraint("Tfilter") + .TypeConstraint("out_type") + .Label(mkl_op_registry::kMklQuantizedOpLabel), + MklQuantizedConv2DSumReluOp); +#endif // INTEL_MKL_ML // Register 2D operations #define REGISTER_MKL_CPU_2D(T) \ REGISTER_KERNEL_BUILDER(Name("_MklConv2D") \ .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ + .TypeConstraint("T") \ .Label(mkl_op_registry::kMklOpLabel), \ - MklConvOp); \ + MklConvOp); \ REGISTER_KERNEL_BUILDER(Name("_MklConv2DWithBias") \ .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ + .TypeConstraint("T") \ .Label(mkl_op_registry::kMklOpLabel), \ - MklConvOp); \ + MklConvOp); \ REGISTER_KERNEL_BUILDER(Name("__MklDummyConv2DWithBias") \ .Device(DEVICE_CPU) \ .TypeConstraint("T") \ @@ -1108,7 +1809,7 @@ TF_CALL_float(REGISTER_MKL_CPU_2D); .Device(DEVICE_CPU) \ .TypeConstraint("T") \ .Label(mkl_op_registry::kMklOpLabel), \ - MklConvOp); + MklConvOp); TF_CALL_float(REGISTER_MKL_CPU_3D); } // namespace tensorflow diff --git a/tensorflow/core/kernels/mkl_quantized_conv_ops.h b/tensorflow/core/kernels/mkl_quantized_conv_ops.h new file mode 100644 index 0000000000..98b14cda5c --- /dev/null +++ b/tensorflow/core/kernels/mkl_quantized_conv_ops.h @@ -0,0 +1,55 @@ +/* Copyright 2015 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#ifndef TENSORFLOW_CORE_KERNELS_MKL_QUANTIZED_CONV_OPS_H_ +#define TENSORFLOW_CORE_KERNELS_MKL_QUANTIZED_CONV_OPS_H_ + +#include "tensorflow/core/framework/tensor.h" +#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor" + +#ifdef INTEL_MKL + +namespace tensorflow { +template +float MklFloatForOneQuantizedLevel(float range_min, float range_max) { + const int64 highest = static_cast(Eigen::NumTraits::highest()); + const int64 lowest = static_cast(Eigen::NumTraits::lowest()); + const float float_for_one_quantized_level = + (range_max - range_min) / (highest - lowest); + return float_for_one_quantized_level; +} + +template +void MklQuantizationRangeForMultiplication(float min_a, float max_a, + float min_b, float max_b, + float* min_c, float* max_c) { + const float a_float_for_one_quant_level = + MklFloatForOneQuantizedLevel(min_a, max_a); + const float b_float_for_one_quant_level = + MklFloatForOneQuantizedLevel(min_b, max_b); + + const int64 c_highest = static_cast(Eigen::NumTraits::highest()); + const int64 c_lowest = static_cast(Eigen::NumTraits::lowest()); + const float c_float_for_one_quant_level = + a_float_for_one_quant_level * b_float_for_one_quant_level; + + *min_c = c_float_for_one_quant_level * c_lowest; + *max_c = c_float_for_one_quant_level * c_highest; +} +} // namespace tensorflow + +#endif // INTEL_MKL + +#endif // TENSORFLOW_CORE_KERNELS_MKL_QUANTIZED_CONV_OPS_H_ diff --git a/tensorflow/core/ops/mkl_nn_ops.cc b/tensorflow/core/ops/mkl_nn_ops.cc new file mode 100644 index 0000000000..9be3470820 --- /dev/null +++ b/tensorflow/core/ops/mkl_nn_ops.cc @@ -0,0 +1,612 @@ +/* Copyright 2015 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "tensorflow/core/framework/common_shape_fns.h" +#include "tensorflow/core/framework/numeric_op.h" +#include "tensorflow/core/framework/op.h" +#include "tensorflow/core/framework/shape_inference.h" +#include "tensorflow/core/util/mirror_pad_mode.h" +#include "tensorflow/core/util/padding.h" +#include "tensorflow/core/util/tensor_format.h" + +// For now, this file only includes MKL quantized ops. In the +// future, we will move all other MKL ops from nn_ops.cc to this file. + +#ifdef INTEL_MKL + +namespace tensorflow { + +using shape_inference::DimensionHandle; +using shape_inference::InferenceContext; +using shape_inference::ShapeHandle; + +REGISTER_OP("_MklQuantizedMaxPool") + .Input("input: T") + .Input("min_input: float") + .Input("max_input: float") + .Input("mkl_input: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Output("output: T") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("T: quantizedtype") + .Attr("ksize: list(int) >= 4") + .Attr("strides: list(int) >= 4") + .Attr(GetPaddingAttrString()) + .SetShapeFn(shape_inference::MaxPoolShape) + .Doc(R"doc( +MKL version of QuantizedMaxPool operator. Uses MKL DNN APIs to perform max pooling +on the quantized input. + +NOTE Do not invoke this operator directly in Python. Graph rewrite pass is +expected to invoke these operators. +)doc"); + +REGISTER_OP("_MklQuantizedAvgPool") + .Input("input: T") + .Input("min_input: float") + .Input("max_input: float") + .Input("mkl_input: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Output("output: T") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("T: quantizedtype") + .Attr("ksize: list(int) >= 4") + .Attr("strides: list(int) >= 4") + .Attr(GetPaddingAttrString()) + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::AvgPoolShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(1), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }) + .Doc(R"doc( +MKL version of QuantizedAvgPool operator. Uses MKL DNN APIs to perform average pooling +on the quantized input. + +NOTE Do not invoke this operator directly in Python. Graph rewrite pass is +expected to invoke these operators. +)doc"); + +REGISTER_OP("_MklQuantizedConv2D") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for enabling MklToTf + // conversion + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Input("mkl_min_freezed_output: uint8") + .Input("mkl_max_freezed_output: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for enabling MklToTf + // conversion + .Attr("out_type: quantizedtype = DT_QINT8") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DWithBias") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: float") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_bias: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for + // enabling MklToTf conversion + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DWithBiasAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: Tbias") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_bias: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Input("mkl_min_freezed_output: uint8") + .Input("mkl_max_freezed_output: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("Tbias: {float, qint32}") + .Attr("T: quantizedtype") // Additional attribute "T" for + // enabling MklToTf conversion + .Attr("out_type: quantizedtype = DT_QINT8") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DAndRelu") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for enabling MklToTf + // conversion + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DAndReluAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Input("mkl_min_freezed_output: uint8") + .Input("mkl_max_freezed_output: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for enabling MklToTf + // conversion + .Attr("out_type: quantizedtype = DT_QUINT8") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DWithBiasAndRelu") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: float") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_bias: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for + // enabling MklToTf conversion + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DWithBiasAndReluAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: Tbias") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_bias: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Input("mkl_min_freezed_output: uint8") + .Input("mkl_max_freezed_output: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("Tbias: {float, qint32}") + .Attr("T: quantizedtype") // Additional attribute "T" for + // enabling MklToTf conversion + .Attr("out_type: quantizedtype = DT_QUINT8") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(8), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DWithBiasSumAndRelu") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: float") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("summand: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_bias: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Input("mkl_summand: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for + // enabling MklToTf conversion + .Attr("out_type: quantizedtype = DT_QINT32") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DWithBiasSumAndReluAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: Tbias") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Input("summand: Tsummand") + .Input("min_summand: float") + .Input("max_summand: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_bias: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Input("mkl_min_freezed_output: uint8") + .Input("mkl_max_freezed_output: uint8") + .Input("mkl_summand: uint8") + .Input("mkl_min_summand: uint8") + .Input("mkl_max_summand: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("Tbias: {float, qint32}") + .Attr("Tsummand: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for + // enabling MklToTf conversion + .Attr("out_type: quantizedtype = DT_QUINT8") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(8), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +REGISTER_OP("_MklQuantizedConv2DWithBiasSignedSumAndReluAndRequantize") + .Input("input: Tinput") + .Input("filter: Tfilter") + .Input("bias: Tbias") + .Input("min_input: float") + .Input("max_input: float") + .Input("min_filter: float") + .Input("max_filter: float") + .Input("min_freezed_output: float") + .Input("max_freezed_output: float") + .Input("summand: Tsummand") + .Input("min_summand: float") + .Input("max_summand: float") + .Input("mkl_input: uint8") + .Input("mkl_filter: uint8") + .Input("mkl_bias: uint8") + .Input("mkl_min_input: uint8") + .Input("mkl_max_input: uint8") + .Input("mkl_min_filter: uint8") + .Input("mkl_max_filter: uint8") + .Input("mkl_min_freezed_output: uint8") + .Input("mkl_max_freezed_output: uint8") + .Input("mkl_summand: uint8") + .Input("mkl_min_summand: uint8") + .Input("mkl_max_summand: uint8") + .Output("output: out_type") + .Output("min_output: float") + .Output("max_output: float") + .Output("mkl_output: uint8") + .Output("mkl_min_output: uint8") + .Output("mkl_max_output: uint8") + .Attr("Tinput: quantizedtype") + .Attr("Tfilter: quantizedtype") + .Attr("Tbias: {float, qint32}") + .Attr("Tsummand: quantizedtype") + .Attr("T: quantizedtype") // Additional attribute "T" for + // enabling MklToTf conversion + .Attr("out_type: quantizedtype = DT_QUINT8") + .Attr("data_format: string = 'NHWC'") + .Attr("strides: list(int)") + .Attr(GetPaddingAttrString()) + .Attr("dilations: list(int) = [1, 1, 1, 1]") + .SetShapeFn([](InferenceContext* c) { + TF_RETURN_IF_ERROR(shape_inference::Conv2DShape(c)); + ShapeHandle unused; + TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 1, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(6), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(7), 0, &unused)); + TF_RETURN_IF_ERROR(c->WithRank(c->input(8), 0, &unused)); + c->set_output(1, c->Scalar()); + c->set_output(2, c->Scalar()); + return Status::OK(); + }); + +} // namespace tensorflow + +#endif // INTEL_MKL diff --git a/tensorflow/core/util/mkl_util.h b/tensorflow/core/util/mkl_util.h index 680211edff..883fa612d5 100644 --- a/tensorflow/core/util/mkl_util.h +++ b/tensorflow/core/util/mkl_util.h @@ -104,6 +104,10 @@ typedef enum { Dim3d_I = 1 } MklDnnDims3D; +typedef enum { + QUANTIZED_VERSION = 0, + FP_VERSION, +} MklQuantization; static const int kSmallBatchSize = 32; #ifdef INTEL_MKL_ML_ONLY @@ -1387,6 +1391,18 @@ template <> memory::data_type MklDnnType() { return memory::data_type::f32; } +template <> +memory::data_type MklDnnType() { + return memory::data_type::u8; +} +template <> +memory::data_type MklDnnType() { + return memory::data_type::s8; +} +template <> +memory::data_type MklDnnType() { + return memory::data_type::s32; +} /// Map TensorFlow's data format into MKL-DNN 3D data format /// @input: TensorFlow data format -- GitLab From 01185324b505dbc2bddf53da93f72d156a81043a Mon Sep 17 00:00:00 2001 From: "Li, Guizi" Date: Fri, 14 Sep 2018 16:42:09 +0800 Subject: [PATCH 0022/1825] remove unnecessary void* --- tensorflow/core/kernels/mkl_relu_op.cc | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/tensorflow/core/kernels/mkl_relu_op.cc b/tensorflow/core/kernels/mkl_relu_op.cc index e010810827..6d55d4abf6 100644 --- a/tensorflow/core/kernels/mkl_relu_op.cc +++ b/tensorflow/core/kernels/mkl_relu_op.cc @@ -1272,16 +1272,14 @@ class MklRelu6Op : public MklReluOpBase { GetMklShape(context, src_index, &dnn_shape_src); Tensor* dst_tensor = nullptr; - void* user_i = - static_cast(const_cast(src_tensor.flat().data())); + T* user_i = const_cast(src_tensor.flat().data()); MklDnnShape dnn_shape_dst; dnn_shape_dst.SetMklTensor(false); AllocateOutputSetMklShape(context, dst_index, &dst_tensor, src_tensor.shape(), dnn_shape_dst); - void* out_o = static_cast(dst_tensor->flat().data()); - (static_cast(out_o))[0] = - std::min(std::max((static_cast(user_i))[0], static_cast(0)), - static_cast(RELU6_UPPER_BOUND)); + T* out_o = dst_tensor->flat().data(); + out_o[0] = std::min(std::max(user_i[0], static_cast(0)), + static_cast(RELU6_UPPER_BOUND)); return; } }; @@ -1311,15 +1309,11 @@ class MklRelu6GradOp dnn_shape_diff_src.SetMklTensor(false); AllocateOutputSetMklShape(context, diff_src_index, &diff_src_tensor, diff_dst_tensor.shape(), dnn_shape_diff_src); - void* out_o = static_cast(diff_src_tensor->flat().data()); - void* user_i = - static_cast(const_cast(src_tensor.flat().data())); - void* user_g = - static_cast(const_cast(diff_dst_tensor.flat().data())); - (static_cast(out_o))[0] = - (static_cast(user_g))[0] * - ((static_cast(user_i))[0] > 0 && - (static_cast(user_i))[0] < static_cast(RELU6_UPPER_BOUND)); + T* out_o = diff_src_tensor->flat().data(); + T* user_i = const_cast(src_tensor.flat().data()); + T* user_g = const_cast(diff_dst_tensor.flat().data()); + out_o[0] = user_g[0] * user_i[0] > 0 && + (user_i[0] < static_cast(RELU6_UPPER_BOUND)); return; } }; -- GitLab From 41adb4d6c787f23f4555a692e54f62b0101a7a6f Mon Sep 17 00:00:00 2001 From: leondgarse Date: Sun, 16 Sep 2018 16:25:42 +0800 Subject: [PATCH 0023/1825] Replace multinomial in comment by argmax Replace 'a multinomial distribution' in comment by 'argmax', and also in the text introduction. --- .../python/examples/generative_examples/text_generation.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb b/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb index 07dbfd3630..ad481175fe 100644 --- a/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb +++ b/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb @@ -567,7 +567,7 @@ "\n", "* We get predictions using the start_string and the hidden state\n", "\n", - "* Then we use a multinomial distribution to calculate the index of the predicted word. **We use this predicted word as our next input to the model**\n", + "* Then we use argmax to calculate the index of the predicted word. **We use this predicted word as our next input to the model**\n", "\n", "* **The hidden state returned by the model is fed back into the model so that it now has more context rather than just one word.** After we predict the next word, the modified hidden states are again fed back into the model, which is how it learns as it gets more context from the previously predicted words.\n", "\n", @@ -603,7 +603,7 @@ "for i in range(num_generate):\n", " predictions, hidden = model(input_eval, hidden)\n", "\n", - " # using a multinomial distribution to predict the word returned by the model\n", + " # using argmax to predict the word returned by the model\n", " predicted_id = tf.argmax(predictions[-1]).numpy()\n", " \n", " # We pass the predicted word as the next input to the model\n", -- GitLab From 14b5eb7d0295060204bf56e041da4c84c44d8cd5 Mon Sep 17 00:00:00 2001 From: leondgarse Date: Sun, 16 Sep 2018 16:34:32 +0800 Subject: [PATCH 0024/1825] Remove a temperature line Remove a temperature line in Next Steps part --- .../python/examples/generative_examples/text_generation.ipynb | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb b/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb index ad481175fe..bda9e77085 100644 --- a/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb +++ b/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb @@ -626,7 +626,6 @@ "\n", "* Change the start string to a different character, or the start of a sentence.\n", "* Experiment with training on a different, or with different parameters. [Project Gutenberg](http://www.gutenberg.org/ebooks/100), for example, contains a large collection of books.\n", - "* Experiment with the temperature parameter.\n", "* Add another RNN layer.\n" ] }, -- GitLab From 50762768ef9d7915ade5cf485d26ffb96753df71 Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Tue, 18 Sep 2018 00:18:57 -0700 Subject: [PATCH 0025/1825] Update CONTRIBUTING.md to reflect PR merge process --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f598999f35..3f62e3f645 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,8 @@ Follow either of the two links above to access the appropriate CLA and instructi If you have improvements to TensorFlow, send us your pull requests! For those just getting started, Github has a [howto](https://help.github.com/articles/using-pull-requests/). -TensorFlow team members will be assigned to review your pull requests. Once the pull requests are approved and pass continuous integration checks, we will merge the pull requests. +TensorFlow team members will be assigned to review your pull requests. Once the pull requests are approved and pass continuous integration checks, a TensorFlow team member will apply `ready to pull` to your change. This means we are working on getting your pull request submitted to our internal repository. After the change has been submitted internally, your pull request will be merged automatically on GitHub. + For some pull requests, we will apply the patch for each pull request to our internal version control system first, and export the change out as a new commit later, at which point the original pull request will be closed. The commits in the pull request will be squashed into a single commit with the pull request creator as the author. These pull requests will be labeled as pending merge internally. If you want to contribute but you're not sure where to start, take a look at the -- GitLab From a8fe42cdfc7341655f61414ce02ddd9d016165ed Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Tue, 18 Sep 2018 00:19:38 -0700 Subject: [PATCH 0026/1825] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f62e3f645..05e970e8cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ Follow either of the two links above to access the appropriate CLA and instructi If you have improvements to TensorFlow, send us your pull requests! For those just getting started, Github has a [howto](https://help.github.com/articles/using-pull-requests/). -TensorFlow team members will be assigned to review your pull requests. Once the pull requests are approved and pass continuous integration checks, a TensorFlow team member will apply `ready to pull` to your change. This means we are working on getting your pull request submitted to our internal repository. After the change has been submitted internally, your pull request will be merged automatically on GitHub. +TensorFlow team members will be assigned to review your pull requests. Once the pull requests are approved and pass continuous integration checks, a TensorFlow team member will apply `ready to pull` label to your change. This means we are working on getting your pull request submitted to our internal repository. After the change has been submitted internally, your pull request will be merged automatically on GitHub. For some pull requests, we will apply the patch for each pull request to our internal version control system first, and export the change out as a new commit later, at which point the original pull request will be closed. The commits in the pull request will be squashed into a single commit with the pull request creator as the author. These pull requests will be labeled as pending merge internally. -- GitLab From 609283c6ea14a7ea1ef9bb9ba4dd5d5608c0b2c0 Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Thu, 20 Sep 2018 18:55:40 +0100 Subject: [PATCH 0027/1825] Add new TF_GraphImportGraphDefWithDevice() function, which only allows to bind newly created graph, drop TF_BindToDevice(), since (re)binding at runtime is being frown upon upstream --- tensorflow/c/c_api.cc | 17 ++++++++++++----- tensorflow/c/c_api.h | 8 +++++++- tensorflow/go/graph.go | 21 ++++++++++----------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc index f559972b18..13d9a8d388 100644 --- a/tensorflow/c/c_api.cc +++ b/tensorflow/c/c_api.cc @@ -2129,13 +2129,20 @@ void TF_GraphImportGraphDef(TF_Graph* graph, const TF_Buffer* graph_def, TF_DeleteImportGraphDefResults(results); } -void TF_BindToDevice(TF_Graph* graph, const char *device) { - if (device == NULL) - return; +void TF_GraphImportGraphDefWithDevice(TF_Graph* graph, const TF_Buffer* graph_def, + const TF_ImportGraphDefOptions* options, + const char *device, + TF_Status* status) { + TF_ImportGraphDefResults* results = + TF_GraphImportGraphDefWithResults(graph, graph_def, options, status); - for (Node *node: graph->graph.nodes()) { - node->set_requested_device(device); + if ((device != NULL) && (TF_GetCode(status) == TF_OK)) { + for (Node *node: graph->graph.nodes()) { + node->set_requested_device(device); + } } + + TF_DeleteImportGraphDefResults(results); } // While loop functions ------------------------------------------------------- diff --git a/tensorflow/c/c_api.h b/tensorflow/c/c_api.h index 24db50eeb6..5fd6bb09e0 100644 --- a/tensorflow/c/c_api.h +++ b/tensorflow/c/c_api.h @@ -1019,7 +1019,13 @@ TF_CAPI_EXPORT extern void TF_GraphImportGraphDef( TF_Graph* graph, const TF_Buffer* graph_def, const TF_ImportGraphDefOptions* options, TF_Status* status); -TF_CAPI_EXPORT extern void TF_BindToDevice(TF_Graph* graph, const char *device); +// Import the graph serialized in `graph_def` into `graph`. +// Convenience function for when no results are needed. +// Optionally try to bind resulted graph to given device ('/gpu:1' for example) +void TF_GraphImportGraphDefWithDevice(TF_Graph* graph, const TF_Buffer* graph_def, + const TF_ImportGraphDefOptions* options, + const char *device, + TF_Status* status); // Adds a copy of function `func` and optionally its gradient function `grad` // to `g`. Once `func`/`grad` is added to `g`, it can be called by creating diff --git a/tensorflow/go/graph.go b/tensorflow/go/graph.go index 6aecba4632..d89d1ad5f5 100644 --- a/tensorflow/go/graph.go +++ b/tensorflow/go/graph.go @@ -114,24 +114,23 @@ func (g *Graph) ImportWithDevice(def []byte, prefix string, device string) error C.memcpy(buf.data, unsafe.Pointer(&def[0]), buf.length) status := newStatus() - C.TF_GraphImportGraphDef(g.c, buf, opts, status.c) + + if len(device) != 0 { + cdev := C.CString(device) + defer C.free(unsafe.Pointer(cdev)) + + C.TF_GraphImportGraphDefWithDevice(g.c, buf, opts, cdev, status.c) + } else { + C.TF_GraphImportGraphDef(g.c, buf, opts, status.c) + } + if err := status.Err(); err != nil { return err } - g.BindToDevice(device) return nil } -func (g *Graph) BindToDevice(device string) { - if len(device) != 0 { - cdev := C.CString(device) - defer C.free(unsafe.Pointer(cdev)) - - C.TF_BindToDevice(g.c, cdev) - } -} - func (g *Graph) Import(def []byte, prefix string) error { return g.ImportWithDevice(def, prefix, "") } -- GitLab From 165537259eb834bb28335ec28397da4488fd1839 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 20 Sep 2018 20:20:50 +0200 Subject: [PATCH 0028/1825] Init commit of Slurm cluster resolver - Slurm cluster resolver for homogeneous job allocation - basic unit tests for task and GPU allocation --- tensorflow/contrib/cluster_resolver/BUILD | 27 +++ .../contrib/cluster_resolver/__init__.py | 2 + .../python/training/__init__.py | 1 + .../python/training/slurm_cluster_resolver.py | 180 ++++++++++++++++ .../training/slurm_cluster_resolver_test.py | 201 ++++++++++++++++++ 5 files changed, 411 insertions(+) create mode 100644 tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py create mode 100644 tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py diff --git a/tensorflow/contrib/cluster_resolver/BUILD b/tensorflow/contrib/cluster_resolver/BUILD index 707f621184..47e287f5bb 100644 --- a/tensorflow/contrib/cluster_resolver/BUILD +++ b/tensorflow/contrib/cluster_resolver/BUILD @@ -31,6 +31,7 @@ py_library( ":base_cluster_resolver_py", ":gce_cluster_resolver_py", ":tpu_cluster_resolver_py", + ":slurm_cluster_resolver_py", "//tensorflow/python:util", ], ) @@ -64,6 +65,16 @@ py_library( ], ) +py_library( + name = "slurm_cluster_resolver_py", + srcs = ["python/training/slurm_cluster_resolver.py"], + srcs_version = "PY2AND3", + deps = [ + ":base_cluster_resolver_py", + "//tensorflow/python:training", + ], +) + tf_py_test( name = "base_cluster_resolver_py_test", srcs = ["python/training/cluster_resolver_test.py"], @@ -109,3 +120,19 @@ tf_py_test( grpc_enabled = True, main = "python/training/tpu_cluster_resolver_test.py", ) + +tf_py_test( + name = "slurm_cluster_resolver_py_test", + size = "small", + srcs = ["python/training/slurm_cluster_resolver_test.py"], + additional_deps = [ + ":cluster_resolver_py", + ":slurm_cluster_resolver_py", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform_test", + "//tensorflow/python:training", + ], + main = "python/training/slurm_cluster_resolver_test.py", +) diff --git a/tensorflow/contrib/cluster_resolver/__init__.py b/tensorflow/contrib/cluster_resolver/__init__.py index b4d8cd4a7c..039f3ee21a 100644 --- a/tensorflow/contrib/cluster_resolver/__init__.py +++ b/tensorflow/contrib/cluster_resolver/__init__.py @@ -25,6 +25,7 @@ from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import UnionClusterResolver from tensorflow.contrib.cluster_resolver.python.training.gce_cluster_resolver import GceClusterResolver from tensorflow.contrib.cluster_resolver.python.training.tpu_cluster_resolver import TPUClusterResolver +from tensorflow.contrib.cluster_resolver.python.training.slurm_cluster_resolver import SlurmClusterResolver # pylint: enable=wildcard-import,unused-import from tensorflow.python.util.all_util import remove_undocumented @@ -35,6 +36,7 @@ _allowed_symbols = [ 'UnionClusterResolver', 'GceClusterResolver', 'TPUClusterResolver', + 'SlurmClusterResolver', ] remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/cluster_resolver/python/training/__init__.py b/tensorflow/contrib/cluster_resolver/python/training/__init__.py index 0b0464b7d2..21009a75da 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/__init__.py +++ b/tensorflow/contrib/cluster_resolver/python/training/__init__.py @@ -23,3 +23,4 @@ from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import UnionClusterResolver from tensorflow.contrib.cluster_resolver.python.training.gce_cluster_resolver import GceClusterResolver from tensorflow.contrib.cluster_resolver.python.training.tpu_cluster_resolver import TPUClusterResolver +from tensorflow.contrib.cluster_resolver.python.training.slurm_cluster_resolver import SlurmClusterResolver diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py new file mode 100644 index 0000000000..6bdba362fc --- /dev/null +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py @@ -0,0 +1,180 @@ +# 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. +# ============================================================================== +"""Implementation of Cluster Resolvers for Slurm workload manager.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import subprocess + +from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import ClusterResolver +from tensorflow.python.training.server_lib import ClusterSpec + +class SlurmClusterResolver(ClusterResolver): + """Cluster Resolver for system with Slurm workload manager. + + This is an implementation of cluster resolvers for Slurm clusters. This allows + the specification of jobs and task counts, number of tasks per node, number of + GPUs on each node and number of GPUs for each task, It retrieves system + attributes by Slurm environment variables, resolve allocated computing node + names, construct a cluster and return a Cluster Resolver object which an be + use for distributed TensorFlow. + """ + + def __init__(self, + jobs, + port_base=8888, + gpus_per_node=1, + gpus_per_task=1, + tasks_per_node=None, + auto_set_gpu=True): + """Creates a new SlurmClusterResolver object. + + This takes in parameters and creates a SlurmClusterResolver object. It uses + those parameters to determine which nodes will processes reside and resolve + their hostnames. With the number of the GPUs on each node and number of GPUs + for each task it offsets the port number for each processes and allocate + GPUs to tasks by setting environment variables. The resolver currently + supports homogeneous tasks and default Slurm process allocation. + + Args: + jobs: Dictionary with job names as key and number of tasks in the job as + value + port_base: The first port number to start with for processes on a node. + gpus_per_node: Number of GPUs avaliable on each node. + gpus_per_task: Number of GPUs to be used for each task. + tasks_per_node: Number of tasks to run on each node, if not set defaults + to Slurm's output environment variable SLURM_NTASKS_PER_NODE. + auto_set_gpu: Set the visible CUDA devices automatically while resolving + the cluster by setting CUDA_VISIBLE_DEVICE environment variable. + Defaults to True. + + Raises: + RuntimeError: If requested more GPUs per node then avaliable or requested + more tasks then assigned tasks. + """ + + # check if launched by mpirun + if 'OMPI_COMM_WORLD_RANK' in os.environ: + self._rank = int(os.environ['OMPI_COMM_WORLD_RANK']) + num_tasks = int(os.environ['OMPI_COMM_WORLD_SIZE']) + else: + self._rank = int(os.environ['SLURM_PROCID']) + num_tasks = int(os.environ['SLURM_NTASKS']) + + self._jobs = jobs + self._port_base = port_base + + # user specification overrides SLURM specification + if tasks_per_node is not None: + self._tasks_per_node = tasks_per_node + elif tasks_per_node is None and 'SLURM_NTASKS_PER_NODE' in os.environ: + self._tasks_per_node = int(os.environ['SLURM_NTASKS_PER_NODE']) + else: + raise RuntimeError('Neither `tasks_per_node` or \ + SLURM_NTASKS_PER_NODE is set') + + self._gpus_per_node = gpus_per_node + self._gpus_per_task = gpus_per_task + + self._auto_set_gpu = auto_set_gpu + self._job_name = None + self._task_index = None + + self._gpu_allocation = [] + self._cluster_allocation = {} + + if self._tasks_per_node * self._gpus_per_task > self._gpus_per_node: + raise RuntimeError('Requested more GPUs per node then avaliable') + + if sum(self._jobs.values()) != num_tasks: + raise RuntimeError('Requested more tasks then assigned tasks') + + def cluster_spec(self): + """Returns a ClusterSpec object based on the latest instance group info. + + This returns a ClusterSpec object for use based on information from the + specified initialization parameters and Slurm environment variables. The + cluster specification is resolved each time this function is called. The + resolver extract hostnames of nodes by scontrol and pack tasks in that + order until a node a has number of tasks that is equal to specification. + GPUs on nodes are allocated to tasks by specification through setting + CUDA_VISIBLE_DEVICE environment variable. + + Returns: + A ClusterSpec containing host information retrieved from Slurm's + environment variables. + """ + hostlist = subprocess.check_output(['scontrol', 'show', 'hostname']).\ + decode("utf-8").strip().split('\n') + + task_list = [] + self._gpu_allocation = [] + self._cluster_allocation = {} + + for host in hostlist: + for port_offset, gpu_offset in zip(range(self._tasks_per_node), + range(0, self._gpus_per_node, + self._gpus_per_task)): + + host_addr = "%s:%d" % (host, self._port_base+port_offset) + task_list.append(host_addr) + gpu_id_list = [] + + for gpu_id in range(gpu_offset, gpu_offset+self._gpus_per_task): + gpu_id_list.append(str(gpu_id)) + + self._gpu_allocation.append(",".join(gpu_id_list)) + + cluster_rank_offset_start = 0 + cluster_rank_offset_end = 0 + + for job_name, num_tasks in self._jobs.items(): + cluster_rank_offset_end = cluster_rank_offset_start + num_tasks + + self._cluster_allocation[job_name] = \ + task_list[cluster_rank_offset_start:cluster_rank_offset_end] + + if self._rank >= cluster_rank_offset_start and \ + self._rank < cluster_rank_offset_end: + + self._job_name = job_name + self._task_index = self._rank - cluster_rank_offset_start + + cluster_rank_offset_start = cluster_rank_offset_end + + if self._auto_set_gpu is True: + os.environ['CUDA_VISIBLE_DEVICE'] = self._gpu_allocation[self._rank] + + return ClusterSpec(self._cluster_allocation) + + def own_task(self): + """Returns job name and task_index for the process which calls this function + + This returns the job name and task index for the process which calls this + function according to its rank and cluster specification. The job name and + task index are set after a cluster is constructed by cluster_spec otherwise + defaults to None. + + Returns: + A string specifying job name the process belongs to and an integner + specifying the task index the process belongs to in that job. + """ + return self._job_name, self._task_index + + def master(self): + return self._cluster_allocation[str(self._job_name)][self._task_index] diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py new file mode 100644 index 0000000000..2e4848d773 --- /dev/null +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py @@ -0,0 +1,201 @@ +# 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 SlurmClusterResolver.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import subprocess +import os + +from tensorflow.contrib.cluster_resolver.python.training.slurm_cluster_resolver import SlurmClusterResolver +from tensorflow.python.platform import test +from tensorflow.python.training import server_lib + +mock = test.mock + +class SlurmClusterResolverTest(test.TestCase): + + def mock_check_subprocess_output(self): + return b't02n13\nt02n41\nt02n43\nt02n44\n' + + def _verifyClusterSpecEquality(self, cluster_spec, expected_proto): + self.assertProtoEquals(expected_proto, cluster_spec.as_cluster_def()) + self.assertProtoEquals( + expected_proto, server_lib.ClusterSpec(cluster_spec).as_cluster_def()) + self.assertProtoEquals( + expected_proto, + server_lib.ClusterSpec(cluster_spec.as_cluster_def()).as_cluster_def()) + self.assertProtoEquals( + expected_proto, + server_lib.ClusterSpec(cluster_spec.as_dict()).as_cluster_def()) + + @mock.patch.dict(os.environ, {'SLURM_PROCID': '0', 'SLURM_NTASKS': '3'}) + @mock.patch.object(subprocess, 'check_output', + mock_check_subprocess_output) + def testSimpleSuccessfulRetrieval(self): + slurm_cluster_resolver = SlurmClusterResolver( + jobs={"ps": 1, "worker": 2}, + port_base=8888, + tasks_per_node=1, + gpus_per_node=1, + gpus_per_task=1, + auto_set_gpu=False) + + actual_cluster_spec = slurm_cluster_resolver.cluster_spec() + expected_proto = """ + job { + name: "ps" + tasks { + value: "t02n13:8888" + } + } + job { + name: "worker" + tasks { + value: "t02n41:8888" + } + tasks { + key: 1 + value: "t02n43:8888" + } + } + """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) + + @mock.patch.dict(os.environ, {'SLURM_PROCID': '0', + 'SLURM_NTASKS': '3', + 'SLURM_NTASKS_PER_NODE': '1'}) + @mock.patch.object(subprocess, 'check_output', + mock_check_subprocess_output) + def testTaskPerNodeNotSetRetrieval(self): + slurm_cluster_resolver = SlurmClusterResolver( + jobs={"ps": 1, "worker": 2}, + port_base=8888, + gpus_per_node=1, + gpus_per_task=1, + auto_set_gpu=False) + + actual_cluster_spec = slurm_cluster_resolver.cluster_spec() + expected_proto = """ + job { + name: "ps" + tasks { + value: "t02n13:8888" + } + } + job { + name: "worker" + tasks { + value: "t02n41:8888" + } + tasks { + key: 1 + value: "t02n43:8888" + } + } + """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) + + @mock.patch.dict(os.environ, {'SLURM_PROCID': '1', + 'SLURM_NTASKS': '5', + 'SLURM_NTASKS_PER_NODE': '2', + 'CUDA_VISIBLE_DEVICE': ''}) + @mock.patch.object(subprocess, 'check_output', + mock_check_subprocess_output) + def testMultiTaskPerNodeRetrieval(self): + slurm_cluster_resolver = SlurmClusterResolver( + jobs={"ps": 1, "worker": 4}, + port_base=8888, + gpus_per_node=2, + gpus_per_task=1, + auto_set_gpu=True) + + actual_cluster_spec = slurm_cluster_resolver.cluster_spec() + expected_proto = """ + job { + name: "ps" + tasks { + value: "t02n13:8888" + } + } + job { + name: "worker" + tasks { + value: "t02n13:8889" + } + tasks { + key: 1 + value: "t02n41:8888" + } + tasks { + key: 2 + value: "t02n41:8889" + } + tasks { + key: 3 + value: "t02n43:8888" + } + } + """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) + assert os.environ['CUDA_VISIBLE_DEVICE'] == '1' + + @mock.patch.dict(os.environ, {'SLURM_PROCID': '1', + 'SLURM_NTASKS': '5', + 'SLURM_NTASKS_PER_NODE': '2', + 'CUDA_VISIBLE_DEVICE': ''}) + @mock.patch.object(subprocess, 'check_output', mock_check_subprocess_output) + def testMultipleGpusPerTaskRetrieval(self): + slurm_cluster_resolver = SlurmClusterResolver( + jobs={"ps": 1, "worker": 4}, + port_base=8888, + gpus_per_node=4, + gpus_per_task=2, + auto_set_gpu=True) + + actual_cluster_spec = slurm_cluster_resolver.cluster_spec() + expected_proto = """ + job { + name: "ps" + tasks { + value: "t02n13:8888" + } + } + job { + name: "worker" + tasks { + value: "t02n13:8889" + } + tasks { + key: 1 + value: "t02n41:8888" + } + tasks { + key: 2 + value: "t02n41:8889" + } + tasks { + key: 3 + value: "t02n43:8888" + } + } + """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) + assert os.environ['CUDA_VISIBLE_DEVICE'] == '2,3' + +if __name__ == '__main__': + test.main() -- GitLab From 3364762e54a8417bcd5ef72a93ba5d0af98b2a01 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 20 Sep 2018 20:43:59 +0200 Subject: [PATCH 0029/1825] Init commit of cluster resolver for slurm workload manager - Slurm cluster resolver for cluster with homogeneous job allocation - Basic unit tests for task and GPU allocation --- tensorflow/contrib/cluster_resolver/BUILD | 27 +++ .../contrib/cluster_resolver/__init__.py | 2 + .../python/training/__init__.py | 1 + .../python/training/slurm_cluster_resolver.py | 180 ++++++++++++++++ .../training/slurm_cluster_resolver_test.py | 201 ++++++++++++++++++ 5 files changed, 411 insertions(+) create mode 100644 tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py create mode 100644 tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py diff --git a/tensorflow/contrib/cluster_resolver/BUILD b/tensorflow/contrib/cluster_resolver/BUILD index 707f621184..47e287f5bb 100644 --- a/tensorflow/contrib/cluster_resolver/BUILD +++ b/tensorflow/contrib/cluster_resolver/BUILD @@ -31,6 +31,7 @@ py_library( ":base_cluster_resolver_py", ":gce_cluster_resolver_py", ":tpu_cluster_resolver_py", + ":slurm_cluster_resolver_py", "//tensorflow/python:util", ], ) @@ -64,6 +65,16 @@ py_library( ], ) +py_library( + name = "slurm_cluster_resolver_py", + srcs = ["python/training/slurm_cluster_resolver.py"], + srcs_version = "PY2AND3", + deps = [ + ":base_cluster_resolver_py", + "//tensorflow/python:training", + ], +) + tf_py_test( name = "base_cluster_resolver_py_test", srcs = ["python/training/cluster_resolver_test.py"], @@ -109,3 +120,19 @@ tf_py_test( grpc_enabled = True, main = "python/training/tpu_cluster_resolver_test.py", ) + +tf_py_test( + name = "slurm_cluster_resolver_py_test", + size = "small", + srcs = ["python/training/slurm_cluster_resolver_test.py"], + additional_deps = [ + ":cluster_resolver_py", + ":slurm_cluster_resolver_py", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform_test", + "//tensorflow/python:training", + ], + main = "python/training/slurm_cluster_resolver_test.py", +) diff --git a/tensorflow/contrib/cluster_resolver/__init__.py b/tensorflow/contrib/cluster_resolver/__init__.py index b4d8cd4a7c..039f3ee21a 100644 --- a/tensorflow/contrib/cluster_resolver/__init__.py +++ b/tensorflow/contrib/cluster_resolver/__init__.py @@ -25,6 +25,7 @@ from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import UnionClusterResolver from tensorflow.contrib.cluster_resolver.python.training.gce_cluster_resolver import GceClusterResolver from tensorflow.contrib.cluster_resolver.python.training.tpu_cluster_resolver import TPUClusterResolver +from tensorflow.contrib.cluster_resolver.python.training.slurm_cluster_resolver import SlurmClusterResolver # pylint: enable=wildcard-import,unused-import from tensorflow.python.util.all_util import remove_undocumented @@ -35,6 +36,7 @@ _allowed_symbols = [ 'UnionClusterResolver', 'GceClusterResolver', 'TPUClusterResolver', + 'SlurmClusterResolver', ] remove_undocumented(__name__, _allowed_symbols) diff --git a/tensorflow/contrib/cluster_resolver/python/training/__init__.py b/tensorflow/contrib/cluster_resolver/python/training/__init__.py index 0b0464b7d2..21009a75da 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/__init__.py +++ b/tensorflow/contrib/cluster_resolver/python/training/__init__.py @@ -23,3 +23,4 @@ from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import UnionClusterResolver from tensorflow.contrib.cluster_resolver.python.training.gce_cluster_resolver import GceClusterResolver from tensorflow.contrib.cluster_resolver.python.training.tpu_cluster_resolver import TPUClusterResolver +from tensorflow.contrib.cluster_resolver.python.training.slurm_cluster_resolver import SlurmClusterResolver diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py new file mode 100644 index 0000000000..6bdba362fc --- /dev/null +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py @@ -0,0 +1,180 @@ +# 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. +# ============================================================================== +"""Implementation of Cluster Resolvers for Slurm workload manager.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import subprocess + +from tensorflow.contrib.cluster_resolver.python.training.cluster_resolver import ClusterResolver +from tensorflow.python.training.server_lib import ClusterSpec + +class SlurmClusterResolver(ClusterResolver): + """Cluster Resolver for system with Slurm workload manager. + + This is an implementation of cluster resolvers for Slurm clusters. This allows + the specification of jobs and task counts, number of tasks per node, number of + GPUs on each node and number of GPUs for each task, It retrieves system + attributes by Slurm environment variables, resolve allocated computing node + names, construct a cluster and return a Cluster Resolver object which an be + use for distributed TensorFlow. + """ + + def __init__(self, + jobs, + port_base=8888, + gpus_per_node=1, + gpus_per_task=1, + tasks_per_node=None, + auto_set_gpu=True): + """Creates a new SlurmClusterResolver object. + + This takes in parameters and creates a SlurmClusterResolver object. It uses + those parameters to determine which nodes will processes reside and resolve + their hostnames. With the number of the GPUs on each node and number of GPUs + for each task it offsets the port number for each processes and allocate + GPUs to tasks by setting environment variables. The resolver currently + supports homogeneous tasks and default Slurm process allocation. + + Args: + jobs: Dictionary with job names as key and number of tasks in the job as + value + port_base: The first port number to start with for processes on a node. + gpus_per_node: Number of GPUs avaliable on each node. + gpus_per_task: Number of GPUs to be used for each task. + tasks_per_node: Number of tasks to run on each node, if not set defaults + to Slurm's output environment variable SLURM_NTASKS_PER_NODE. + auto_set_gpu: Set the visible CUDA devices automatically while resolving + the cluster by setting CUDA_VISIBLE_DEVICE environment variable. + Defaults to True. + + Raises: + RuntimeError: If requested more GPUs per node then avaliable or requested + more tasks then assigned tasks. + """ + + # check if launched by mpirun + if 'OMPI_COMM_WORLD_RANK' in os.environ: + self._rank = int(os.environ['OMPI_COMM_WORLD_RANK']) + num_tasks = int(os.environ['OMPI_COMM_WORLD_SIZE']) + else: + self._rank = int(os.environ['SLURM_PROCID']) + num_tasks = int(os.environ['SLURM_NTASKS']) + + self._jobs = jobs + self._port_base = port_base + + # user specification overrides SLURM specification + if tasks_per_node is not None: + self._tasks_per_node = tasks_per_node + elif tasks_per_node is None and 'SLURM_NTASKS_PER_NODE' in os.environ: + self._tasks_per_node = int(os.environ['SLURM_NTASKS_PER_NODE']) + else: + raise RuntimeError('Neither `tasks_per_node` or \ + SLURM_NTASKS_PER_NODE is set') + + self._gpus_per_node = gpus_per_node + self._gpus_per_task = gpus_per_task + + self._auto_set_gpu = auto_set_gpu + self._job_name = None + self._task_index = None + + self._gpu_allocation = [] + self._cluster_allocation = {} + + if self._tasks_per_node * self._gpus_per_task > self._gpus_per_node: + raise RuntimeError('Requested more GPUs per node then avaliable') + + if sum(self._jobs.values()) != num_tasks: + raise RuntimeError('Requested more tasks then assigned tasks') + + def cluster_spec(self): + """Returns a ClusterSpec object based on the latest instance group info. + + This returns a ClusterSpec object for use based on information from the + specified initialization parameters and Slurm environment variables. The + cluster specification is resolved each time this function is called. The + resolver extract hostnames of nodes by scontrol and pack tasks in that + order until a node a has number of tasks that is equal to specification. + GPUs on nodes are allocated to tasks by specification through setting + CUDA_VISIBLE_DEVICE environment variable. + + Returns: + A ClusterSpec containing host information retrieved from Slurm's + environment variables. + """ + hostlist = subprocess.check_output(['scontrol', 'show', 'hostname']).\ + decode("utf-8").strip().split('\n') + + task_list = [] + self._gpu_allocation = [] + self._cluster_allocation = {} + + for host in hostlist: + for port_offset, gpu_offset in zip(range(self._tasks_per_node), + range(0, self._gpus_per_node, + self._gpus_per_task)): + + host_addr = "%s:%d" % (host, self._port_base+port_offset) + task_list.append(host_addr) + gpu_id_list = [] + + for gpu_id in range(gpu_offset, gpu_offset+self._gpus_per_task): + gpu_id_list.append(str(gpu_id)) + + self._gpu_allocation.append(",".join(gpu_id_list)) + + cluster_rank_offset_start = 0 + cluster_rank_offset_end = 0 + + for job_name, num_tasks in self._jobs.items(): + cluster_rank_offset_end = cluster_rank_offset_start + num_tasks + + self._cluster_allocation[job_name] = \ + task_list[cluster_rank_offset_start:cluster_rank_offset_end] + + if self._rank >= cluster_rank_offset_start and \ + self._rank < cluster_rank_offset_end: + + self._job_name = job_name + self._task_index = self._rank - cluster_rank_offset_start + + cluster_rank_offset_start = cluster_rank_offset_end + + if self._auto_set_gpu is True: + os.environ['CUDA_VISIBLE_DEVICE'] = self._gpu_allocation[self._rank] + + return ClusterSpec(self._cluster_allocation) + + def own_task(self): + """Returns job name and task_index for the process which calls this function + + This returns the job name and task index for the process which calls this + function according to its rank and cluster specification. The job name and + task index are set after a cluster is constructed by cluster_spec otherwise + defaults to None. + + Returns: + A string specifying job name the process belongs to and an integner + specifying the task index the process belongs to in that job. + """ + return self._job_name, self._task_index + + def master(self): + return self._cluster_allocation[str(self._job_name)][self._task_index] diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py new file mode 100644 index 0000000000..2e4848d773 --- /dev/null +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py @@ -0,0 +1,201 @@ +# 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 SlurmClusterResolver.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import subprocess +import os + +from tensorflow.contrib.cluster_resolver.python.training.slurm_cluster_resolver import SlurmClusterResolver +from tensorflow.python.platform import test +from tensorflow.python.training import server_lib + +mock = test.mock + +class SlurmClusterResolverTest(test.TestCase): + + def mock_check_subprocess_output(self): + return b't02n13\nt02n41\nt02n43\nt02n44\n' + + def _verifyClusterSpecEquality(self, cluster_spec, expected_proto): + self.assertProtoEquals(expected_proto, cluster_spec.as_cluster_def()) + self.assertProtoEquals( + expected_proto, server_lib.ClusterSpec(cluster_spec).as_cluster_def()) + self.assertProtoEquals( + expected_proto, + server_lib.ClusterSpec(cluster_spec.as_cluster_def()).as_cluster_def()) + self.assertProtoEquals( + expected_proto, + server_lib.ClusterSpec(cluster_spec.as_dict()).as_cluster_def()) + + @mock.patch.dict(os.environ, {'SLURM_PROCID': '0', 'SLURM_NTASKS': '3'}) + @mock.patch.object(subprocess, 'check_output', + mock_check_subprocess_output) + def testSimpleSuccessfulRetrieval(self): + slurm_cluster_resolver = SlurmClusterResolver( + jobs={"ps": 1, "worker": 2}, + port_base=8888, + tasks_per_node=1, + gpus_per_node=1, + gpus_per_task=1, + auto_set_gpu=False) + + actual_cluster_spec = slurm_cluster_resolver.cluster_spec() + expected_proto = """ + job { + name: "ps" + tasks { + value: "t02n13:8888" + } + } + job { + name: "worker" + tasks { + value: "t02n41:8888" + } + tasks { + key: 1 + value: "t02n43:8888" + } + } + """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) + + @mock.patch.dict(os.environ, {'SLURM_PROCID': '0', + 'SLURM_NTASKS': '3', + 'SLURM_NTASKS_PER_NODE': '1'}) + @mock.patch.object(subprocess, 'check_output', + mock_check_subprocess_output) + def testTaskPerNodeNotSetRetrieval(self): + slurm_cluster_resolver = SlurmClusterResolver( + jobs={"ps": 1, "worker": 2}, + port_base=8888, + gpus_per_node=1, + gpus_per_task=1, + auto_set_gpu=False) + + actual_cluster_spec = slurm_cluster_resolver.cluster_spec() + expected_proto = """ + job { + name: "ps" + tasks { + value: "t02n13:8888" + } + } + job { + name: "worker" + tasks { + value: "t02n41:8888" + } + tasks { + key: 1 + value: "t02n43:8888" + } + } + """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) + + @mock.patch.dict(os.environ, {'SLURM_PROCID': '1', + 'SLURM_NTASKS': '5', + 'SLURM_NTASKS_PER_NODE': '2', + 'CUDA_VISIBLE_DEVICE': ''}) + @mock.patch.object(subprocess, 'check_output', + mock_check_subprocess_output) + def testMultiTaskPerNodeRetrieval(self): + slurm_cluster_resolver = SlurmClusterResolver( + jobs={"ps": 1, "worker": 4}, + port_base=8888, + gpus_per_node=2, + gpus_per_task=1, + auto_set_gpu=True) + + actual_cluster_spec = slurm_cluster_resolver.cluster_spec() + expected_proto = """ + job { + name: "ps" + tasks { + value: "t02n13:8888" + } + } + job { + name: "worker" + tasks { + value: "t02n13:8889" + } + tasks { + key: 1 + value: "t02n41:8888" + } + tasks { + key: 2 + value: "t02n41:8889" + } + tasks { + key: 3 + value: "t02n43:8888" + } + } + """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) + assert os.environ['CUDA_VISIBLE_DEVICE'] == '1' + + @mock.patch.dict(os.environ, {'SLURM_PROCID': '1', + 'SLURM_NTASKS': '5', + 'SLURM_NTASKS_PER_NODE': '2', + 'CUDA_VISIBLE_DEVICE': ''}) + @mock.patch.object(subprocess, 'check_output', mock_check_subprocess_output) + def testMultipleGpusPerTaskRetrieval(self): + slurm_cluster_resolver = SlurmClusterResolver( + jobs={"ps": 1, "worker": 4}, + port_base=8888, + gpus_per_node=4, + gpus_per_task=2, + auto_set_gpu=True) + + actual_cluster_spec = slurm_cluster_resolver.cluster_spec() + expected_proto = """ + job { + name: "ps" + tasks { + value: "t02n13:8888" + } + } + job { + name: "worker" + tasks { + value: "t02n13:8889" + } + tasks { + key: 1 + value: "t02n41:8888" + } + tasks { + key: 2 + value: "t02n41:8889" + } + tasks { + key: 3 + value: "t02n43:8888" + } + } + """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) + assert os.environ['CUDA_VISIBLE_DEVICE'] == '2,3' + +if __name__ == '__main__': + test.main() -- GitLab From 7009a1584bed82a860b9b0af946dbbaa72255763 Mon Sep 17 00:00:00 2001 From: steven Date: Thu, 20 Sep 2018 22:26:05 +0200 Subject: [PATCH 0030/1825] change method name own_task to get_task_info --- .../cluster_resolver/python/training/slurm_cluster_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py index 6bdba362fc..c3109263dc 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py @@ -162,7 +162,7 @@ class SlurmClusterResolver(ClusterResolver): return ClusterSpec(self._cluster_allocation) - def own_task(self): + def get_task_info(self): """Returns job name and task_index for the process which calls this function This returns the job name and task index for the process which calls this -- GitLab From 1b7338162a7ad7535f2d715c93bc5d634d33be41 Mon Sep 17 00:00:00 2001 From: Steven Date: Sun, 23 Sep 2018 17:37:36 +0200 Subject: [PATCH 0031/1825] Minor syntax changes and wrapping subprocess call as private method --- .../python/training/slurm_cluster_resolver.py | 24 ++++++++++++----- .../training/slurm_cluster_resolver_test.py | 27 ++++++++++--------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py index 6bdba362fc..5855afdec2 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py @@ -30,11 +30,21 @@ class SlurmClusterResolver(ClusterResolver): This is an implementation of cluster resolvers for Slurm clusters. This allows the specification of jobs and task counts, number of tasks per node, number of GPUs on each node and number of GPUs for each task, It retrieves system - attributes by Slurm environment variables, resolve allocated computing node + attributes by Slurm environment variables, resolves allocated computing node names, construct a cluster and return a Cluster Resolver object which an be use for distributed TensorFlow. """ + def _resolve_hostnames(self): + """Resolve host names of nodes allocated in current jobs. + + Returns: + A list of node names as strings. + """ + hostlist = subprocess.check_output(['scontrol', 'show', 'hostname']).\ + decode('utf-8').strip().split('\n') + return hostlist + def __init__(self, jobs, port_base=8888, @@ -45,7 +55,7 @@ class SlurmClusterResolver(ClusterResolver): """Creates a new SlurmClusterResolver object. This takes in parameters and creates a SlurmClusterResolver object. It uses - those parameters to determine which nodes will processes reside and resolve + those parameters to check which nodes will processes reside and resolves their hostnames. With the number of the GPUs on each node and number of GPUs for each task it offsets the port number for each processes and allocate GPUs to tasks by setting environment variables. The resolver currently @@ -63,6 +73,9 @@ class SlurmClusterResolver(ClusterResolver): the cluster by setting CUDA_VISIBLE_DEVICE environment variable. Defaults to True. + Returns: + A ClusterResolver object which can be used with distributed TensorFlow. + Raises: RuntimeError: If requested more GPUs per node then avaliable or requested more tasks then assigned tasks. @@ -119,8 +132,7 @@ class SlurmClusterResolver(ClusterResolver): A ClusterSpec containing host information retrieved from Slurm's environment variables. """ - hostlist = subprocess.check_output(['scontrol', 'show', 'hostname']).\ - decode("utf-8").strip().split('\n') + hostlist = self._resolve_hostnames() task_list = [] self._gpu_allocation = [] @@ -131,14 +143,14 @@ class SlurmClusterResolver(ClusterResolver): range(0, self._gpus_per_node, self._gpus_per_task)): - host_addr = "%s:%d" % (host, self._port_base+port_offset) + host_addr = '%s:%d' % (host, self._port_base+port_offset) task_list.append(host_addr) gpu_id_list = [] for gpu_id in range(gpu_offset, gpu_offset+self._gpus_per_task): gpu_id_list.append(str(gpu_id)) - self._gpu_allocation.append(",".join(gpu_id_list)) + self._gpu_allocation.append(','.join(gpu_id_list)) cluster_rank_offset_start = 0 cluster_rank_offset_end = 0 diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py index 2e4848d773..c2296eb4fe 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py @@ -29,8 +29,8 @@ mock = test.mock class SlurmClusterResolverTest(test.TestCase): - def mock_check_subprocess_output(self): - return b't02n13\nt02n41\nt02n43\nt02n44\n' + def mock_resolve_hostnames_output(self): + return ['t02n13','t02n41','t02n43','t02n44'] def _verifyClusterSpecEquality(self, cluster_spec, expected_proto): self.assertProtoEquals(expected_proto, cluster_spec.as_cluster_def()) @@ -44,11 +44,11 @@ class SlurmClusterResolverTest(test.TestCase): server_lib.ClusterSpec(cluster_spec.as_dict()).as_cluster_def()) @mock.patch.dict(os.environ, {'SLURM_PROCID': '0', 'SLURM_NTASKS': '3'}) - @mock.patch.object(subprocess, 'check_output', - mock_check_subprocess_output) + @mock.patch.object(SlurmClusterResolver, '_resolve_hostnames', + mock_resolve_hostnames_output) def testSimpleSuccessfulRetrieval(self): slurm_cluster_resolver = SlurmClusterResolver( - jobs={"ps": 1, "worker": 2}, + jobs={'ps': 1, 'worker': 2}, port_base=8888, tasks_per_node=1, gpus_per_node=1, @@ -79,11 +79,11 @@ class SlurmClusterResolverTest(test.TestCase): @mock.patch.dict(os.environ, {'SLURM_PROCID': '0', 'SLURM_NTASKS': '3', 'SLURM_NTASKS_PER_NODE': '1'}) - @mock.patch.object(subprocess, 'check_output', - mock_check_subprocess_output) + @mock.patch.object(SlurmClusterResolver, '_resolve_hostnames', + mock_resolve_hostnames_output) def testTaskPerNodeNotSetRetrieval(self): slurm_cluster_resolver = SlurmClusterResolver( - jobs={"ps": 1, "worker": 2}, + jobs={'ps': 1, 'worker': 2}, port_base=8888, gpus_per_node=1, gpus_per_task=1, @@ -114,11 +114,11 @@ class SlurmClusterResolverTest(test.TestCase): 'SLURM_NTASKS': '5', 'SLURM_NTASKS_PER_NODE': '2', 'CUDA_VISIBLE_DEVICE': ''}) - @mock.patch.object(subprocess, 'check_output', - mock_check_subprocess_output) + @mock.patch.object(SlurmClusterResolver, '_resolve_hostnames', + mock_resolve_hostnames_output) def testMultiTaskPerNodeRetrieval(self): slurm_cluster_resolver = SlurmClusterResolver( - jobs={"ps": 1, "worker": 4}, + jobs={'ps': 1, 'worker': 4}, port_base=8888, gpus_per_node=2, gpus_per_task=1, @@ -158,10 +158,11 @@ class SlurmClusterResolverTest(test.TestCase): 'SLURM_NTASKS': '5', 'SLURM_NTASKS_PER_NODE': '2', 'CUDA_VISIBLE_DEVICE': ''}) - @mock.patch.object(subprocess, 'check_output', mock_check_subprocess_output) + @mock.patch.object(SlurmClusterResolver, '_resolve_hostnames', + mock_resolve_hostnames_output) def testMultipleGpusPerTaskRetrieval(self): slurm_cluster_resolver = SlurmClusterResolver( - jobs={"ps": 1, "worker": 4}, + jobs={'ps': 1, 'worker': 4}, port_base=8888, gpus_per_node=4, gpus_per_task=2, -- GitLab From fd6f499af6ba986607a857124b75b2bebc4576e5 Mon Sep 17 00:00:00 2001 From: Steven Date: Sun, 23 Sep 2018 20:46:35 +0200 Subject: [PATCH 0032/1825] Added documentation to slurm resolver --- .../python/training/README.slurm | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tensorflow/contrib/cluster_resolver/python/training/README.slurm diff --git a/tensorflow/contrib/cluster_resolver/python/training/README.slurm b/tensorflow/contrib/cluster_resolver/python/training/README.slurm new file mode 100644 index 0000000000..3a7675f250 --- /dev/null +++ b/tensorflow/contrib/cluster_resolver/python/training/README.slurm @@ -0,0 +1,50 @@ +# Slurm Cluster Resolver + +The Slurm Cluster Resolver resolves cluster specification for distribution TensorFlow work launched on HPC system running on Slurm. This implementation is able to handle homogeneous task allocation on computing nodes with default task distribution plane. The resolution is done by determining job configuration through a number of Slurm output variables and user input. The resolver requires the specification of total number of tasks launched, process ID/rank of the running process, number of tasks launched per node, number of GPUs present on each node and the number of GPUs to allocate for each task. + +The process ID/rank is extracted from environment variable ```SLURM_PROCID``` and the total number of tasks launched is extract from ```SLURM_NTASKS```. The number of tasks per node is extracted from ```SLURM_NTASKS_PER_NODE```, unless a value is specified by user. The number of GPUs present on each node and number of GPUs for each task have to be specified by the user. A base port can be specified by user and in case there are more than one task launched per node the port number will be incremented for each additional tasks on that node. The hostnames are resolved by running command ```scontrol show hostname``` through a subprocess and a list of hostnames will be returned. The distribution of rank/process ID by default follows that order. By default allocated GPUs will be automatically exposed to processes according to specification by setting ```CUDA_VISIBLE_DEVICE```. + +## Example +- Slurm allocation in shell ```salloc --nodes=2 -t 01:30:00 -A --ntasks-per-node=2 --gres=gpu:k80:2 --exclusive``` +- Creating cluster in Python +``` +cluster_resolver = tf.contrib.cluster_resolver.SlurmClusterResolver( + {'ps': 1, 'worker': 3}, + port_base=8888, + tasks_per_node=2, + gpus_per_node=2, + gpus_per_task=1, + auto_set_gpu=True) + +cluster = cluster_resolver.cluster_spec() +job_name, task_index = cluster_resolver.get_task_info() +``` +The above example resolves a cluster specification for a Slurm job allocation with two computing nodes each having two GPUs and two tasks will be launched on each node. The jobs are specified in form of a dictionary where the key is a string representing job name and value is an integer that specifies the number of tasks in that job. ```cluster_resolver.cluster_spec()``` will return a cluster specificaiton object and the cluster specification will have the following specification as protobuf. + +``` +job { + name: "ps" + tasks { + value: "t02n13:8888" + } +} +job { + name: "worker" + tasks { + value: "t02n13:8889" + } + tasks { + key: 1 + value: "t02n41:8888" + } + tasks { + key: 2 + value: "t02n41:8889" + } +} +``` + +After calling ```cluster_resolver.cluster_spec()``` internal data structions of the resolver will be populated. By looking at the process ID/rank and comparing with cluster specification the task can 'realize' which task it belongs to. This can be retrieved by calling ```cluster_resolver.get_task_info()``` and a string specifying job name and an integer specifying the task index will be returned. + +GPUs will be automatically allocated to the processes. For example in the above example ``` +t02n41:8888``` will see GPU 0 and ```t02n41:8889``` will see GPU 1. -- GitLab From c475edf9513d6bceae992775869fb9dd0b2c848a Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 24 Sep 2018 05:43:36 +0000 Subject: [PATCH 0033/1825] Fix for tf.keras.regularizers.{l1,l2}(0.) with tf.get_variable This fix tries to address the issue in 22470 where tf.keras.regularizers.{l1,l2}(l=0.) with tf.get_variable returns ``` AttributeError: 'float' object has no attribute 'name' ``` The issue only happens when `l=0.` as in that case, `regularization = 0.` was returned directly (and `0.` does not have a `name` attribute as a float number) This fix convert regularization = 0. to tensor. This fix fixes 22470. Signed-off-by: Yong Tang --- tensorflow/python/keras/regularizers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/keras/regularizers.py b/tensorflow/python/keras/regularizers.py index 28b6ad4c65..0d139d748c 100644 --- a/tensorflow/python/keras/regularizers.py +++ b/tensorflow/python/keras/regularizers.py @@ -20,6 +20,7 @@ from __future__ import print_function import six +from tensorflow.python.framework import ops 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 @@ -54,7 +55,7 @@ class L1L2(Regularizer): self.l2 = K.cast_to_floatx(l2) def __call__(self, x): - regularization = 0. + regularization = ops.convert_to_tensor(0.) if self.l1: regularization += math_ops.reduce_sum(self.l1 * math_ops.abs(x)) if self.l2: -- GitLab From 7f51123b0e8ab0532af04a5ccad3ab9605128db4 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Mon, 24 Sep 2018 05:51:09 +0000 Subject: [PATCH 0034/1825] Add dtype to convert_to_tensor Signed-off-by: Yong Tang --- tensorflow/python/keras/regularizers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/regularizers.py b/tensorflow/python/keras/regularizers.py index 0d139d748c..fd0748f3ae 100644 --- a/tensorflow/python/keras/regularizers.py +++ b/tensorflow/python/keras/regularizers.py @@ -55,7 +55,7 @@ class L1L2(Regularizer): self.l2 = K.cast_to_floatx(l2) def __call__(self, x): - regularization = ops.convert_to_tensor(0.) + regularization = ops.convert_to_tensor(0., dtype=K.floatx()) if self.l1: regularization += math_ops.reduce_sum(self.l1 * math_ops.abs(x)) if self.l2: -- GitLab From 8b545abcbb06ec6d674cf18cfe69cad577fc3ade Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 24 Sep 2018 22:55:06 +0200 Subject: [PATCH 0035/1825] fixed spelling error on variables --- .../python/training/slurm_cluster_resolver.py | 6 +++--- .../python/training/slurm_cluster_resolver_test.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py index 92abcde6fc..49204214a8 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver.py @@ -70,7 +70,7 @@ class SlurmClusterResolver(ClusterResolver): tasks_per_node: Number of tasks to run on each node, if not set defaults to Slurm's output environment variable SLURM_NTASKS_PER_NODE. auto_set_gpu: Set the visible CUDA devices automatically while resolving - the cluster by setting CUDA_VISIBLE_DEVICE environment variable. + the cluster by setting CUDA_VISIBLE_DEVICES environment variable. Defaults to True. Returns: @@ -126,7 +126,7 @@ class SlurmClusterResolver(ClusterResolver): resolver extract hostnames of nodes by scontrol and pack tasks in that order until a node a has number of tasks that is equal to specification. GPUs on nodes are allocated to tasks by specification through setting - CUDA_VISIBLE_DEVICE environment variable. + CUDA_VISIBLE_DEVICES environment variable. Returns: A ClusterSpec containing host information retrieved from Slurm's @@ -170,7 +170,7 @@ class SlurmClusterResolver(ClusterResolver): cluster_rank_offset_start = cluster_rank_offset_end if self._auto_set_gpu is True: - os.environ['CUDA_VISIBLE_DEVICE'] = self._gpu_allocation[self._rank] + os.environ['CUDA_VISIBLE_DEVICES'] = self._gpu_allocation[self._rank] return ClusterSpec(self._cluster_allocation) diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py index c2296eb4fe..a3581c1554 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py @@ -113,7 +113,7 @@ class SlurmClusterResolverTest(test.TestCase): @mock.patch.dict(os.environ, {'SLURM_PROCID': '1', 'SLURM_NTASKS': '5', 'SLURM_NTASKS_PER_NODE': '2', - 'CUDA_VISIBLE_DEVICE': ''}) + 'CUDA_VISIBLE_DEVICES': ''}) @mock.patch.object(SlurmClusterResolver, '_resolve_hostnames', mock_resolve_hostnames_output) def testMultiTaskPerNodeRetrieval(self): @@ -152,12 +152,12 @@ class SlurmClusterResolverTest(test.TestCase): } """ self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) - assert os.environ['CUDA_VISIBLE_DEVICE'] == '1' + assert os.environ['CUDA_VISIBLE_DEVICES'] == '1' @mock.patch.dict(os.environ, {'SLURM_PROCID': '1', 'SLURM_NTASKS': '5', 'SLURM_NTASKS_PER_NODE': '2', - 'CUDA_VISIBLE_DEVICE': ''}) + 'CUDA_VISIBLE_DEVICES': ''}) @mock.patch.object(SlurmClusterResolver, '_resolve_hostnames', mock_resolve_hostnames_output) def testMultipleGpusPerTaskRetrieval(self): @@ -196,7 +196,7 @@ class SlurmClusterResolverTest(test.TestCase): } """ self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) - assert os.environ['CUDA_VISIBLE_DEVICE'] == '2,3' + assert os.environ['CUDA_VISIBLE_DEVICES'] == '2,3' if __name__ == '__main__': test.main() -- GitLab From 945eeee62ede6d04546245ed5b5505289d8f171f Mon Sep 17 00:00:00 2001 From: steven Date: Mon, 24 Sep 2018 23:35:31 +0200 Subject: [PATCH 0036/1825] Fixed syntax style for Ubuntu Sanity test --- tensorflow/contrib/cluster_resolver/BUILD | 2 +- .../python/training/slurm_cluster_resolver_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/cluster_resolver/BUILD b/tensorflow/contrib/cluster_resolver/BUILD index 47e287f5bb..7b59f9774e 100644 --- a/tensorflow/contrib/cluster_resolver/BUILD +++ b/tensorflow/contrib/cluster_resolver/BUILD @@ -30,8 +30,8 @@ py_library( deps = [ ":base_cluster_resolver_py", ":gce_cluster_resolver_py", - ":tpu_cluster_resolver_py", ":slurm_cluster_resolver_py", + ":tpu_cluster_resolver_py", "//tensorflow/python:util", ], ) diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py index a3581c1554..fe3a886cd4 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py @@ -30,7 +30,7 @@ mock = test.mock class SlurmClusterResolverTest(test.TestCase): def mock_resolve_hostnames_output(self): - return ['t02n13','t02n41','t02n43','t02n44'] + return ['t02n13', 't02n41', 't02n43', 't02n44'] def _verifyClusterSpecEquality(self, cluster_spec, expected_proto): self.assertProtoEquals(expected_proto, cluster_spec.as_cluster_def()) -- GitLab From e346358d552d3f1b2a4acb86ddecd93b0c2cd356 Mon Sep 17 00:00:00 2001 From: Pan Daoxin Date: Wed, 26 Sep 2018 09:43:29 +0800 Subject: [PATCH 0037/1825] Enable reorder cache in MklTranspose. --- tensorflow/core/kernels/mkl_transpose_op.cc | 2 +- tensorflow/core/util/mkl_util.h | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/mkl_transpose_op.cc b/tensorflow/core/kernels/mkl_transpose_op.cc index 6bbe271c54..be13384f17 100644 --- a/tensorflow/core/kernels/mkl_transpose_op.cc +++ b/tensorflow/core/kernels/mkl_transpose_op.cc @@ -144,7 +144,7 @@ Status MKLTransposeND(OpKernelContext* context, const Tensor& in_tensor, out.SetUsrMem(in_dims, out_strides, out_tensor); std::vector net; - net.push_back(in.CreateReorder(in.GetUsrMem(), out.GetUsrMem())); + net.push_back(FindOrCreateReorder(in.GetUsrMem(), out.GetUsrMem())); stream(stream::kind::eager).submit(net).wait(); return Status::OK(); } catch (mkldnn::error& e) { diff --git a/tensorflow/core/util/mkl_util.h b/tensorflow/core/util/mkl_util.h index cf7ffd8149..1e56753414 100644 --- a/tensorflow/core/util/mkl_util.h +++ b/tensorflow/core/util/mkl_util.h @@ -2171,15 +2171,26 @@ class MklReorderPrimitiveFactory : public MklPrimitiveFactory { FactoryKeyCreator key_creator; auto const &from_desc = from->get_primitive_desc().desc().data; auto const &to_desc = to->get_primitive_desc().desc().data; + const int KIdxFirstStride = 0; memory::dims from_dims(from_desc.dims, &from_desc.dims[from_desc.ndims]); memory::dims to_dims(to_desc.dims, &to_desc.dims[to_desc.ndims]); + memory::dims from_strides(from_desc.layout_desc.blocking + .strides[KIdxFirstStride], + &from_desc.layout_desc.blocking + .strides[KIdxFirstStride][from_desc.ndims]); + memory::dims to_strides(to_desc.layout_desc.blocking + .strides[KIdxFirstStride], + &to_desc.layout_desc.blocking + .strides[KIdxFirstStride][to_desc.ndims]); key_creator.AddAsKey(prefix); key_creator.AddAsKey(static_cast(from_desc.format)); key_creator.AddAsKey(static_cast(from_desc.data_type)); key_creator.AddAsKey(from_dims); + key_creator.AddAsKey(from_strides); key_creator.AddAsKey(static_cast(to_desc.format)); key_creator.AddAsKey(static_cast(to_desc.data_type)); key_creator.AddAsKey(to_dims); + key_creator.AddAsKey(to_strides); return key_creator.GetKey(); } -- GitLab From 125b3b264ba2169ec8944bbf12cb91e495d85d76 Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Wed, 26 Sep 2018 16:38:57 +0100 Subject: [PATCH 0038/1825] Do not create new import function, use options instead --- tensorflow/c/c_api.cc | 20 ++++---------------- tensorflow/c/c_api.h | 13 +++++-------- tensorflow/core/graph/graph_constructor.cc | 11 ++++++++++- tensorflow/core/graph/graph_constructor.h | 3 +++ tensorflow/go/graph.go | 16 +++++++--------- 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc index 13d9a8d388..8ba7f26924 100644 --- a/tensorflow/c/c_api.cc +++ b/tensorflow/c/c_api.cc @@ -1942,6 +1942,10 @@ void TF_ImportGraphDefOptionsSetPrefix(TF_ImportGraphDefOptions* opts, const char* prefix) { opts->opts.prefix = prefix; } +void TF_ImportGraphDefOptionsSetBindDevice(TF_ImportGraphDefOptions* opts, + const char* device) { + opts->opts.bind_device = device; +} void TF_ImportGraphDefOptionsSetUniquifyNames(TF_ImportGraphDefOptions* opts, unsigned char uniquify_names) { @@ -2129,22 +2133,6 @@ void TF_GraphImportGraphDef(TF_Graph* graph, const TF_Buffer* graph_def, TF_DeleteImportGraphDefResults(results); } -void TF_GraphImportGraphDefWithDevice(TF_Graph* graph, const TF_Buffer* graph_def, - const TF_ImportGraphDefOptions* options, - const char *device, - TF_Status* status) { - TF_ImportGraphDefResults* results = - TF_GraphImportGraphDefWithResults(graph, graph_def, options, status); - - if ((device != NULL) && (TF_GetCode(status) == TF_OK)) { - for (Node *node: graph->graph.nodes()) { - node->set_requested_device(device); - } - } - - TF_DeleteImportGraphDefResults(results); -} - // While loop functions ------------------------------------------------------- namespace { diff --git a/tensorflow/c/c_api.h b/tensorflow/c/c_api.h index 5fd6bb09e0..cd0a4acf6c 100644 --- a/tensorflow/c/c_api.h +++ b/tensorflow/c/c_api.h @@ -900,6 +900,11 @@ TF_CAPI_EXPORT extern void TF_DeleteImportGraphDefOptions( TF_CAPI_EXPORT extern void TF_ImportGraphDefOptionsSetPrefix( TF_ImportGraphDefOptions* opts, const char* prefix); +// Set bind device for the nodes in the `graph_def` that will be imported into `graph`. +// `device` is copied and has no lifetime requirements. +TF_CAPI_EXPORT extern void TF_ImportGraphDefOptionsSetBindDevice( + TF_ImportGraphDefOptions* opts, const char* device); + // Set whether to uniquify imported operation names. If true, imported operation // names will be modified if their name already exists in the graph. If false, // conflicting names will be treated as an error. Note that this option has no @@ -1019,14 +1024,6 @@ TF_CAPI_EXPORT extern void TF_GraphImportGraphDef( TF_Graph* graph, const TF_Buffer* graph_def, const TF_ImportGraphDefOptions* options, TF_Status* status); -// Import the graph serialized in `graph_def` into `graph`. -// Convenience function for when no results are needed. -// Optionally try to bind resulted graph to given device ('/gpu:1' for example) -void TF_GraphImportGraphDefWithDevice(TF_Graph* graph, const TF_Buffer* graph_def, - const TF_ImportGraphDefOptions* options, - const char *device, - TF_Status* status); - // Adds a copy of function `func` and optionally its gradient function `grad` // to `g`. Once `func`/`grad` is added to `g`, it can be called by creating // an operation using the function's name. diff --git a/tensorflow/core/graph/graph_constructor.cc b/tensorflow/core/graph/graph_constructor.cc index 7399613f6a..8a1adb1c92 100644 --- a/tensorflow/core/graph/graph_constructor.cc +++ b/tensorflow/core/graph/graph_constructor.cc @@ -86,7 +86,8 @@ class GraphConstructor { return_nodes(in.return_nodes), importing(true), validate_colocation_constraints(in.validate_colocation_constraints), - validate_shape(in.validate_shape) {} + validate_shape(in.validate_shape), + bind_device(in.bind_device) {} bool allow_internal_ops; bool expect_device_spec; @@ -111,6 +112,8 @@ class GraphConstructor { bool importing; bool validate_colocation_constraints; bool validate_shape = true; + + std::string bind_device; }; typedef gtl::ArraySlice NodeDefSlice; @@ -963,11 +966,17 @@ Status GraphConstructor::Convert() { // Note that input_already_exists can grow here AddControlDependencies(&imported_node_def, &input_already_exists); } + if (!opts_.bind_device.empty()) { + imported_node_def.set_device(opts_.bind_device); + } + node_def = &imported_node_def; } else { node_def = &original_node_def; } + + DCHECK_EQ(node_def->input_size(), input_already_exists.size()); TF_RETURN_IF_ERROR(ValidateColocationConstraints(*node_def)); for (int i = 0; i < node_def->input_size(); ++i) { diff --git a/tensorflow/core/graph/graph_constructor.h b/tensorflow/core/graph/graph_constructor.h index f6e41faf9c..445be92a0c 100644 --- a/tensorflow/core/graph/graph_constructor.h +++ b/tensorflow/core/graph/graph_constructor.h @@ -138,6 +138,9 @@ struct ImportGraphDefOptions { // with ops that are not defined in the binary calling ImportGraphDef. // Similar to the producer_op_list argument to import_graph_def in the // python API. + + // Try to bind grapth to given device. + std::string bind_device; }; // Optional results that may be returned by ImportGraphDef. diff --git a/tensorflow/go/graph.go b/tensorflow/go/graph.go index d89d1ad5f5..953ea9ec4e 100644 --- a/tensorflow/go/graph.go +++ b/tensorflow/go/graph.go @@ -100,6 +100,12 @@ func (g *Graph) ImportWithDevice(def []byte, prefix string, device string) error defer C.TF_DeleteImportGraphDefOptions(opts) C.TF_ImportGraphDefOptionsSetPrefix(opts, cprefix) + if len(device) != 0 { + cdev := C.CString(device) + defer C.free(unsafe.Pointer(cdev)) + C.TF_ImportGraphDefOptionsSetBindDevice(opts, cdev) + } + buf := C.TF_NewBuffer() defer C.TF_DeleteBuffer(buf) // Would have preferred to use C.CBytes, but that does not play well @@ -115,15 +121,7 @@ func (g *Graph) ImportWithDevice(def []byte, prefix string, device string) error status := newStatus() - if len(device) != 0 { - cdev := C.CString(device) - defer C.free(unsafe.Pointer(cdev)) - - C.TF_GraphImportGraphDefWithDevice(g.c, buf, opts, cdev, status.c) - } else { - C.TF_GraphImportGraphDef(g.c, buf, opts, status.c) - } - + C.TF_GraphImportGraphDef(g.c, buf, opts, status.c) if err := status.Err(); err != nil { return err } -- GitLab From 4c0c4bb3fe0d68833cf7888e1c164b20d9bfcea0 Mon Sep 17 00:00:00 2001 From: Sergei Lebedev Date: Thu, 27 Sep 2018 16:52:51 +0200 Subject: [PATCH 0039/1825] Added chief to the default device_filters for /job:ps This behaviour matches the description in the _get_default_session_config_distributed docstring, and restores the symmetry of the default device_filters. --- tensorflow/python/estimator/run_config.py | 2 +- tensorflow/python/estimator/run_config_test.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tensorflow/python/estimator/run_config.py b/tensorflow/python/estimator/run_config.py index 3773810a04..1995f50733 100644 --- a/tensorflow/python/estimator/run_config.py +++ b/tensorflow/python/estimator/run_config.py @@ -566,7 +566,7 @@ class RunConfig(object): elif self._task_type == TaskType.WORKER: device_filters = ['/job:ps', '/job:worker/task:%d' % self._task_id] elif self._task_type == TaskType.PS: - device_filters = ['/job:ps', '/job:worker', '/job:master'] + device_filters = ['/job:ps', '/job:worker', '/job:chief', '/job:master'] else: # If the task_type is `EVALUATOR` or something other than the ones in # TaskType then don't set any device filters. diff --git a/tensorflow/python/estimator/run_config_test.py b/tensorflow/python/estimator/run_config_test.py index 06df7cb9dd..313bf62c05 100644 --- a/tensorflow/python/estimator/run_config_test.py +++ b/tensorflow/python/estimator/run_config_test.py @@ -1196,8 +1196,9 @@ class RunConfigSessionConfigTest(test.TestCase): } } run_config = _create_run_config_with_cluster_spec(tf_config) - self._assert_equal_session_config(run_config.session_config, - ['/job:ps', '/job:worker', '/job:master']) + self._assert_equal_session_config( + run_config.session_config, + ['/job:ps', '/job:worker', '/job:chief', '/job:master']) def test_evaluator_session_config(self): tf_config = { -- GitLab From 27489419e8d8870163f5173f77ea56aa118689d8 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 27 Sep 2018 23:46:02 -0700 Subject: [PATCH 0040/1825] Update the relative file pathes in the comments of tf.data kernel files --- tensorflow/core/kernels/data/batch_dataset_op.cc | 2 +- tensorflow/core/kernels/data/cache_dataset_ops.cc | 2 +- tensorflow/core/kernels/data/concatenate_dataset_op.cc | 2 +- tensorflow/core/kernels/data/dataset_ops.cc | 2 +- .../core/kernels/data/dense_to_sparse_batch_dataset_op.cc | 2 +- tensorflow/core/kernels/data/filter_by_component_dataset_op.cc | 2 +- tensorflow/core/kernels/data/filter_dataset_op.cc | 2 +- tensorflow/core/kernels/data/flat_map_dataset_op.cc | 2 +- tensorflow/core/kernels/data/generator_dataset_op.cc | 2 +- tensorflow/core/kernels/data/group_by_reducer_dataset_op.cc | 2 +- tensorflow/core/kernels/data/group_by_window_dataset_op.cc | 2 +- tensorflow/core/kernels/data/interleave_dataset_op.cc | 2 +- tensorflow/core/kernels/data/iterator_ops.cc | 2 +- tensorflow/core/kernels/data/map_and_batch_dataset_op.cc | 2 +- tensorflow/core/kernels/data/map_dataset_op.cc | 2 +- tensorflow/core/kernels/data/optimize_dataset_op.cc | 2 +- tensorflow/core/kernels/data/padded_batch_dataset_op.cc | 2 +- tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc | 2 +- tensorflow/core/kernels/data/parallel_map_dataset_op.cc | 2 +- tensorflow/core/kernels/data/parse_example_dataset_op.cc | 2 +- tensorflow/core/kernels/data/prefetch_dataset_op.cc | 2 +- tensorflow/core/kernels/data/random_dataset_op.cc | 2 +- tensorflow/core/kernels/data/range_dataset_op.cc | 2 +- tensorflow/core/kernels/data/reader_dataset_ops.cc | 2 +- tensorflow/core/kernels/data/repeat_dataset_op.cc | 2 +- tensorflow/core/kernels/data/scan_dataset_op.cc | 2 +- tensorflow/core/kernels/data/shuffle_dataset_op.cc | 2 +- tensorflow/core/kernels/data/skip_dataset_op.cc | 2 +- tensorflow/core/kernels/data/slide_dataset_op.cc | 2 +- tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op.cc | 2 +- tensorflow/core/kernels/data/sql_dataset_ops.cc | 2 +- tensorflow/core/kernels/data/take_dataset_op.cc | 2 +- tensorflow/core/kernels/data/tensor_dataset_op.cc | 2 +- tensorflow/core/kernels/data/tensor_slice_dataset_op.cc | 2 +- tensorflow/core/kernels/data/unbatch_dataset_op.cc | 2 +- tensorflow/core/kernels/data/window_dataset_op.cc | 2 +- tensorflow/core/kernels/data/zip_dataset_op.cc | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/tensorflow/core/kernels/data/batch_dataset_op.cc b/tensorflow/core/kernels/data/batch_dataset_op.cc index d1db1d7bec..023cf79966 100644 --- a/tensorflow/core/kernels/data/batch_dataset_op.cc +++ b/tensorflow/core/kernels/data/batch_dataset_op.cc @@ -21,7 +21,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class BatchDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/cache_dataset_ops.cc b/tensorflow/core/kernels/data/cache_dataset_ops.cc index 34c6c86538..d86d96b9fc 100644 --- a/tensorflow/core/kernels/data/cache_dataset_ops.cc +++ b/tensorflow/core/kernels/data/cache_dataset_ops.cc @@ -23,7 +23,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level description of +// See documentation in ../../ops/dataset_ops.cc for a high-level description of // the following op. class CacheDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/concatenate_dataset_op.cc b/tensorflow/core/kernels/data/concatenate_dataset_op.cc index a04f150e71..46df039530 100644 --- a/tensorflow/core/kernels/data/concatenate_dataset_op.cc +++ b/tensorflow/core/kernels/data/concatenate_dataset_op.cc @@ -20,7 +20,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class ConcatenateDatasetOp : public BinaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/dataset_ops.cc b/tensorflow/core/kernels/data/dataset_ops.cc index bd1ccd5b5d..c689a119c3 100644 --- a/tensorflow/core/kernels/data/dataset_ops.cc +++ b/tensorflow/core/kernels/data/dataset_ops.cc @@ -21,7 +21,7 @@ limitations under the License. namespace tensorflow { namespace data { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class DatasetToGraphOp : public OpKernel { public: diff --git a/tensorflow/core/kernels/data/dense_to_sparse_batch_dataset_op.cc b/tensorflow/core/kernels/data/dense_to_sparse_batch_dataset_op.cc index 237511a07d..45678aa84f 100644 --- a/tensorflow/core/kernels/data/dense_to_sparse_batch_dataset_op.cc +++ b/tensorflow/core/kernels/data/dense_to_sparse_batch_dataset_op.cc @@ -21,7 +21,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class DenseToSparseBatchDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/filter_by_component_dataset_op.cc b/tensorflow/core/kernels/data/filter_by_component_dataset_op.cc index a7e3a56727..d09904a0eb 100644 --- a/tensorflow/core/kernels/data/filter_by_component_dataset_op.cc +++ b/tensorflow/core/kernels/data/filter_by_component_dataset_op.cc @@ -24,7 +24,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. // TODO(prazek): Filter already has a logic of filtering by the given tensor, // but it must return both components. We could introduce kernel like diff --git a/tensorflow/core/kernels/data/filter_dataset_op.cc b/tensorflow/core/kernels/data/filter_dataset_op.cc index 00884314a9..a35f9a021c 100644 --- a/tensorflow/core/kernels/data/filter_dataset_op.cc +++ b/tensorflow/core/kernels/data/filter_dataset_op.cc @@ -26,7 +26,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class FilterDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/flat_map_dataset_op.cc b/tensorflow/core/kernels/data/flat_map_dataset_op.cc index 2fada22a21..5d8565e745 100644 --- a/tensorflow/core/kernels/data/flat_map_dataset_op.cc +++ b/tensorflow/core/kernels/data/flat_map_dataset_op.cc @@ -24,7 +24,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class FlatMapDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/generator_dataset_op.cc b/tensorflow/core/kernels/data/generator_dataset_op.cc index b4367d5a11..5de2e2871d 100644 --- a/tensorflow/core/kernels/data/generator_dataset_op.cc +++ b/tensorflow/core/kernels/data/generator_dataset_op.cc @@ -25,7 +25,7 @@ limitations under the License. namespace tensorflow { namespace data { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class GeneratorDatasetOp::Dataset : public DatasetBase { diff --git a/tensorflow/core/kernels/data/group_by_reducer_dataset_op.cc b/tensorflow/core/kernels/data/group_by_reducer_dataset_op.cc index e7244ee208..87600d7873 100644 --- a/tensorflow/core/kernels/data/group_by_reducer_dataset_op.cc +++ b/tensorflow/core/kernels/data/group_by_reducer_dataset_op.cc @@ -25,7 +25,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class GroupByReducerDatasetOp : public UnaryDatasetOpKernel { public: diff --git a/tensorflow/core/kernels/data/group_by_window_dataset_op.cc b/tensorflow/core/kernels/data/group_by_window_dataset_op.cc index 14aefe5d54..7363664982 100644 --- a/tensorflow/core/kernels/data/group_by_window_dataset_op.cc +++ b/tensorflow/core/kernels/data/group_by_window_dataset_op.cc @@ -26,7 +26,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class GroupByWindowDatasetOp : public UnaryDatasetOpKernel { public: diff --git a/tensorflow/core/kernels/data/interleave_dataset_op.cc b/tensorflow/core/kernels/data/interleave_dataset_op.cc index 0aa802b874..83c1f7b719 100644 --- a/tensorflow/core/kernels/data/interleave_dataset_op.cc +++ b/tensorflow/core/kernels/data/interleave_dataset_op.cc @@ -24,7 +24,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class InterleaveDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/iterator_ops.cc b/tensorflow/core/kernels/data/iterator_ops.cc index 7a833668ac..50b72f46c2 100644 --- a/tensorflow/core/kernels/data/iterator_ops.cc +++ b/tensorflow/core/kernels/data/iterator_ops.cc @@ -39,7 +39,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following ops. const char kIteratorVariantTypeName[] = "tensorflow::Iterator"; diff --git a/tensorflow/core/kernels/data/map_and_batch_dataset_op.cc b/tensorflow/core/kernels/data/map_and_batch_dataset_op.cc index 2bbf4af664..b48a2c3eca 100644 --- a/tensorflow/core/kernels/data/map_and_batch_dataset_op.cc +++ b/tensorflow/core/kernels/data/map_and_batch_dataset_op.cc @@ -34,7 +34,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class MapAndBatchDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/map_dataset_op.cc b/tensorflow/core/kernels/data/map_dataset_op.cc index f112e1dc43..1122d7918c 100644 --- a/tensorflow/core/kernels/data/map_dataset_op.cc +++ b/tensorflow/core/kernels/data/map_dataset_op.cc @@ -23,7 +23,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class MapDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/optimize_dataset_op.cc b/tensorflow/core/kernels/data/optimize_dataset_op.cc index d5b725eac9..58d68d9de0 100644 --- a/tensorflow/core/kernels/data/optimize_dataset_op.cc +++ b/tensorflow/core/kernels/data/optimize_dataset_op.cc @@ -36,7 +36,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class OptimizeDatasetOp : public UnaryDatasetOpKernel { public: diff --git a/tensorflow/core/kernels/data/padded_batch_dataset_op.cc b/tensorflow/core/kernels/data/padded_batch_dataset_op.cc index 7b01c3b4e0..d0943a583e 100644 --- a/tensorflow/core/kernels/data/padded_batch_dataset_op.cc +++ b/tensorflow/core/kernels/data/padded_batch_dataset_op.cc @@ -22,7 +22,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class PaddedBatchDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc b/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc index 2e6e0465f7..e180e510b7 100644 --- a/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc +++ b/tensorflow/core/kernels/data/parallel_interleave_dataset_op.cc @@ -31,7 +31,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class ParallelInterleaveDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/parallel_map_dataset_op.cc b/tensorflow/core/kernels/data/parallel_map_dataset_op.cc index 6abe6c8338..c4e2fecc6e 100644 --- a/tensorflow/core/kernels/data/parallel_map_dataset_op.cc +++ b/tensorflow/core/kernels/data/parallel_map_dataset_op.cc @@ -27,7 +27,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class ParallelMapDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/parse_example_dataset_op.cc b/tensorflow/core/kernels/data/parse_example_dataset_op.cc index c28c06da62..0d77dfe24e 100644 --- a/tensorflow/core/kernels/data/parse_example_dataset_op.cc +++ b/tensorflow/core/kernels/data/parse_example_dataset_op.cc @@ -23,7 +23,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class ParseExampleDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/prefetch_dataset_op.cc b/tensorflow/core/kernels/data/prefetch_dataset_op.cc index 754ed772db..e1d42a9a6b 100644 --- a/tensorflow/core/kernels/data/prefetch_dataset_op.cc +++ b/tensorflow/core/kernels/data/prefetch_dataset_op.cc @@ -26,7 +26,7 @@ limitations under the License. namespace tensorflow { namespace data { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class PrefetchDatasetOp::Dataset : public DatasetBase { diff --git a/tensorflow/core/kernels/data/random_dataset_op.cc b/tensorflow/core/kernels/data/random_dataset_op.cc index 044a791a3f..bcd26ab389 100644 --- a/tensorflow/core/kernels/data/random_dataset_op.cc +++ b/tensorflow/core/kernels/data/random_dataset_op.cc @@ -24,7 +24,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class RandomDatasetOp : public DatasetOpKernel { diff --git a/tensorflow/core/kernels/data/range_dataset_op.cc b/tensorflow/core/kernels/data/range_dataset_op.cc index 89fbaae369..0c0cb5ddc1 100644 --- a/tensorflow/core/kernels/data/range_dataset_op.cc +++ b/tensorflow/core/kernels/data/range_dataset_op.cc @@ -20,7 +20,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class RangeDatasetOp : public DatasetOpKernel { diff --git a/tensorflow/core/kernels/data/reader_dataset_ops.cc b/tensorflow/core/kernels/data/reader_dataset_ops.cc index c474cb4773..df4fbfc69a 100644 --- a/tensorflow/core/kernels/data/reader_dataset_ops.cc +++ b/tensorflow/core/kernels/data/reader_dataset_ops.cc @@ -26,7 +26,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following ops. class TextLineDatasetOp : public DatasetOpKernel { diff --git a/tensorflow/core/kernels/data/repeat_dataset_op.cc b/tensorflow/core/kernels/data/repeat_dataset_op.cc index 94e96635ab..e43dc1f6d8 100644 --- a/tensorflow/core/kernels/data/repeat_dataset_op.cc +++ b/tensorflow/core/kernels/data/repeat_dataset_op.cc @@ -20,7 +20,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class RepeatDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/scan_dataset_op.cc b/tensorflow/core/kernels/data/scan_dataset_op.cc index 2a911aa368..c49a265b51 100644 --- a/tensorflow/core/kernels/data/scan_dataset_op.cc +++ b/tensorflow/core/kernels/data/scan_dataset_op.cc @@ -26,7 +26,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class ScanDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/shuffle_dataset_op.cc b/tensorflow/core/kernels/data/shuffle_dataset_op.cc index 66466d6a36..038d9cb9bd 100644 --- a/tensorflow/core/kernels/data/shuffle_dataset_op.cc +++ b/tensorflow/core/kernels/data/shuffle_dataset_op.cc @@ -30,7 +30,7 @@ namespace { const int64 kLogIntervalMicros = 10 * 1000000; // 10 seconds. -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class ShuffleDatasetOpBase : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/skip_dataset_op.cc b/tensorflow/core/kernels/data/skip_dataset_op.cc index b8c7fb15f4..bfaa632a74 100644 --- a/tensorflow/core/kernels/data/skip_dataset_op.cc +++ b/tensorflow/core/kernels/data/skip_dataset_op.cc @@ -20,7 +20,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class SkipDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/slide_dataset_op.cc b/tensorflow/core/kernels/data/slide_dataset_op.cc index 1e73cfc753..2be7fd7410 100644 --- a/tensorflow/core/kernels/data/slide_dataset_op.cc +++ b/tensorflow/core/kernels/data/slide_dataset_op.cc @@ -26,7 +26,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class SlideDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op.cc b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op.cc index 85b1e50695..ccb125f3c3 100644 --- a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op.cc +++ b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op.cc @@ -24,7 +24,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. template diff --git a/tensorflow/core/kernels/data/sql_dataset_ops.cc b/tensorflow/core/kernels/data/sql_dataset_ops.cc index 6bbe459332..a50a041f5d 100644 --- a/tensorflow/core/kernels/data/sql_dataset_ops.cc +++ b/tensorflow/core/kernels/data/sql_dataset_ops.cc @@ -27,7 +27,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following ops. class SqlDatasetOp : public DatasetOpKernel { diff --git a/tensorflow/core/kernels/data/take_dataset_op.cc b/tensorflow/core/kernels/data/take_dataset_op.cc index e5cdfdd732..e8570b68c9 100644 --- a/tensorflow/core/kernels/data/take_dataset_op.cc +++ b/tensorflow/core/kernels/data/take_dataset_op.cc @@ -20,7 +20,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class TakeDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/tensor_dataset_op.cc b/tensorflow/core/kernels/data/tensor_dataset_op.cc index ca4ea25b89..ee0fb0069a 100644 --- a/tensorflow/core/kernels/data/tensor_dataset_op.cc +++ b/tensorflow/core/kernels/data/tensor_dataset_op.cc @@ -21,7 +21,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class TensorDatasetOp : public DatasetOpKernel { diff --git a/tensorflow/core/kernels/data/tensor_slice_dataset_op.cc b/tensorflow/core/kernels/data/tensor_slice_dataset_op.cc index 7dc64b0a75..fe2f5ea536 100644 --- a/tensorflow/core/kernels/data/tensor_slice_dataset_op.cc +++ b/tensorflow/core/kernels/data/tensor_slice_dataset_op.cc @@ -22,7 +22,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class TensorSliceDatasetOp : public DatasetOpKernel { diff --git a/tensorflow/core/kernels/data/unbatch_dataset_op.cc b/tensorflow/core/kernels/data/unbatch_dataset_op.cc index 81c432b938..9d0abc21ef 100644 --- a/tensorflow/core/kernels/data/unbatch_dataset_op.cc +++ b/tensorflow/core/kernels/data/unbatch_dataset_op.cc @@ -21,7 +21,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class UnbatchDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/window_dataset_op.cc b/tensorflow/core/kernels/data/window_dataset_op.cc index ac44623ce2..16698c0b1a 100644 --- a/tensorflow/core/kernels/data/window_dataset_op.cc +++ b/tensorflow/core/kernels/data/window_dataset_op.cc @@ -22,7 +22,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class WindowDatasetOp : public UnaryDatasetOpKernel { diff --git a/tensorflow/core/kernels/data/zip_dataset_op.cc b/tensorflow/core/kernels/data/zip_dataset_op.cc index 61a2078f46..4186cb4ecd 100644 --- a/tensorflow/core/kernels/data/zip_dataset_op.cc +++ b/tensorflow/core/kernels/data/zip_dataset_op.cc @@ -20,7 +20,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class ZipDatasetOp : public DatasetOpKernel { -- GitLab From a3ff15205c20b9bcaf9afc85fe45e33fe3259af4 Mon Sep 17 00:00:00 2001 From: HuiyangFei Date: Fri, 28 Sep 2018 13:26:08 -0700 Subject: [PATCH 0041/1825] Fix concat primitive descriptor creation bug When there is tensor format mismatch between tf tensor and mkl tensor, the primitive descriptor creation will error out. This fix allows MKL concat op to decide what format to use on its own. It first creates concat w/o dst_md, then queries dst_pd from concat. --- tensorflow/core/kernels/mkl_concat_op.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/mkl_concat_op.cc b/tensorflow/core/kernels/mkl_concat_op.cc index 8ad7ebb51f..a41f7f08ae 100644 --- a/tensorflow/core/kernels/mkl_concat_op.cc +++ b/tensorflow/core/kernels/mkl_concat_op.cc @@ -776,7 +776,8 @@ class MklConcatOp : public OpKernel { if (are_all_mkl_inputs) concat_dim = mkl_input_shapes[0].TfDimIdx(concat_dim); - auto concat_pd = concat::primitive_desc(dst_md, concat_dim, srcs_pd); + auto concat_pd = concat::primitive_desc(concat_dim, srcs_pd); + auto dst_pd = concat_pd.dst_primitive_desc(); MklDnnShape dnn_shape_dst; TensorShape tf_shape_dst; -- GitLab From ae83549ec0bbae4924115ddc0e097c114ef144c4 Mon Sep 17 00:00:00 2001 From: Roger Iyengar Date: Sat, 29 Sep 2018 14:19:42 -0400 Subject: [PATCH 0042/1825] Fixed bug in tflitecamerademo It seems that the top K labels should be printed after the inference has been run and the filter has been applied. I therefore moved the call to printTopKLabels after this happens, into a block labeled "Print the results." --- .../com/example/android/tflitecamerademo/ImageClassifier.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java b/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java index 2d11a57434..39057aa776 100644 --- a/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java +++ b/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java @@ -112,8 +112,6 @@ public abstract class ImageClassifier { /** Classifies a frame from the preview stream. */ void classifyFrame(Bitmap bitmap, SpannableStringBuilder builder) { - printTopKLabels(builder); - if (tflite == null) { Log.e(TAG, "Image classifier has not been initialized; Skipped."); builder.append(new SpannableString("Uninitialized Classifier.")); @@ -129,6 +127,7 @@ public abstract class ImageClassifier { applyFilter(); // Print the results. + printTopKLabels(builder); long duration = endTime - startTime; SpannableString span = new SpannableString(duration + " ms"); span.setSpan(new ForegroundColorSpan(android.graphics.Color.LTGRAY), 0, span.length(), 0); -- GitLab From e26263af36a8e3dcff7581022b40b06e1a1e33ed Mon Sep 17 00:00:00 2001 From: mdfaijul Date: Wed, 3 Oct 2018 11:11:37 -0700 Subject: [PATCH 0043/1825] Fixed style with clang-format --- tensorflow/core/kernels/mkl_conv_ops.cc | 200 ++++++++++++------------ 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index 95b6dc066c..dfad990aac 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -323,21 +323,21 @@ 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 new primitive */ conv_fwd = new MklConvFwdPrimitive( convFwdDims); } else { // try to find a suitable one in pool - conv_fwd = - dynamic_cast*>( - MklConvFwdPrimitiveFactory::GetInstance() - .GetConvFwd(convFwdDims)); + conv_fwd = dynamic_cast< + MklConvFwdPrimitive*>( + MklConvFwdPrimitiveFactory::GetInstance() + .GetConvFwd(convFwdDims)); if (conv_fwd == nullptr) { conv_fwd = new MklConvFwdPrimitive( convFwdDims); MklConvFwdPrimitiveFactory::GetInstance() + Toutput>::GetInstance() .SetConvFwd(convFwdDims, conv_fwd); } } @@ -425,15 +425,16 @@ class MklConvOp : public OpKernel { OP_REQUIRES(context, FormatFromString(data_format, &data_format_), errors::InvalidArgument("Invalid data format")); OP_REQUIRES(context, strides_.size() == 4, - errors::InvalidArgument("Sliding window strides field must " - "specify 4 dimensions")); + errors::InvalidArgument( + "Sliding window strides field must " + "specify 4 dimensions")); const int64 stride_n = GetTensorDim(strides_, data_format_, 'N'); const int64 stride_c = GetTensorDim(strides_, data_format_, 'C'); - 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(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("padding", &padding_)); } @@ -467,19 +468,18 @@ class MklConvOp : public OpKernel { filter.shape().DebugString())); for (int i = 0; i < 3; i++) { - OP_REQUIRES( - context, - FastBoundsCheck(filter.dim_size(i), std::numeric_limits::max()), - errors::InvalidArgument("filter too large")); + OP_REQUIRES(context, FastBoundsCheck(filter.dim_size(i), + std::numeric_limits::max()), + errors::InvalidArgument("filter too large")); } const int64 input_depth = input_in_mkl_format ? GetMklTensorDim(mkl_context.input_shape, 'C') : GetTensorDim(input, data_format_, 'C'); - OP_REQUIRES(context, input_depth == filter.dim_size(2), - errors::InvalidArgument( - "input and filter must have the same depth: ", input_depth, - " vs ", filter.dim_size(2))); + OP_REQUIRES( + context, input_depth == filter.dim_size(2), + errors::InvalidArgument("input and filter must have the same depth: ", + input_depth, " vs ", filter.dim_size(2))); // The last dimension for filter is out_depth. const int out_depth = static_cast(filter.dim_size(3)); @@ -488,10 +488,9 @@ class MklConvOp : public OpKernel { const int64 input_rows_raw = input_in_mkl_format ? GetMklTensorDim(mkl_context.input_shape, 'H') : GetTensorDim(input, data_format_, 'H'); - OP_REQUIRES( - context, - FastBoundsCheck(input_rows_raw, std::numeric_limits::max()), - errors::InvalidArgument("Input rows too large")); + OP_REQUIRES(context, FastBoundsCheck(input_rows_raw, + std::numeric_limits::max()), + errors::InvalidArgument("Input rows too large")); const int input_rows = static_cast(input_rows_raw); const int filter_rows = static_cast(filter.dim_size(0)); @@ -500,10 +499,9 @@ class MklConvOp : public OpKernel { const int64 input_cols_raw = input_in_mkl_format ? GetMklTensorDim(mkl_context.input_shape, 'W') : GetTensorDim(input, data_format_, 'W'); - OP_REQUIRES( - context, - FastBoundsCheck(input_cols_raw, std::numeric_limits::max()), - errors::InvalidArgument("Input cols too large")); + OP_REQUIRES(context, FastBoundsCheck(input_cols_raw, + std::numeric_limits::max()), + errors::InvalidArgument("Input cols too large")); const int input_cols = static_cast(input_cols_raw); const int filter_cols = static_cast(filter.dim_size(1)); @@ -511,10 +509,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 @@ -732,7 +729,7 @@ class MklConvOp : public OpKernel { mkl_prim_convert_input; dnnLayout_t mkl_lt_internal_filter, mkl_lt_internal_bias, mkl_lt_internal_input; - void *mkl_buf_convert_input, *mkl_buf_convert_filter, + void* mkl_buf_convert_input, *mkl_buf_convert_filter, *mkl_buf_convert_bias; mkl_prim_convert_filter = nullptr; mkl_prim_convert_bias = nullptr; @@ -865,21 +862,23 @@ class MklConvOp : public OpKernel { OP_REQUIRES(context, FormatFromString(data_format, &data_format_), errors::InvalidArgument("Invalid data format")); OP_REQUIRES(context, (strides_.size() == 4 || strides_.size() == 5), - errors::InvalidArgument("Sliding window strides field must " - "specify 4 or 5 dimensions")); + errors::InvalidArgument( + "Sliding window strides field must " + "specify 4 or 5 dimensions")); const int64 stride_n = GetTensorDim(strides_, data_format_, 'N'); const int64 stride_c = GetTensorDim(strides_, data_format_, 'C'); - 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(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("padding", &padding_)); if (strides_.size() == 4) { OP_REQUIRES(context, dilations_.size() == 4, - errors::InvalidArgument("Sliding window dilations field must " - "specify 4 dimensions")); + errors::InvalidArgument( + "Sliding window dilations field must " + "specify 4 dimensions")); const int64 dilation_n = GetTensorDim(dilations_, data_format_, 'N'); const int64 dilation_c = GetTensorDim(dilations_, data_format_, 'C'); const int64 dilation_h = GetTensorDim(dilations_, data_format_, 'H'); @@ -893,19 +892,18 @@ class MklConvOp : public OpKernel { errors::InvalidArgument("Dilated rates should be larger than 0.")); } else if (strides_.size() == 5) { 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), + errors::InvalidArgument( + "Dilation rates field must " + "specify 5 dimensions")); + 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.")); } } @@ -920,25 +918,26 @@ class MklConvOp : public OpKernel { GetMklShape(context, kInputIndex_Src, &src_mkl_shape); GetMklShape(context, kInputIndex_Filter, &filter_mkl_shape); OP_REQUIRES(context, filter_mkl_shape.IsMklTensor() == false, - errors::InvalidArgument("Filter should not be in " - "Mkl Layout")); + errors::InvalidArgument( + "Filter should not be in " + "Mkl Layout")); MklDnnData src(&cpu_engine_); MklDnnData filter(&cpu_engine_); memory::dims src_dims, filter_dims, padding_left, padding_right, - dilations, strides; + dilations, strides; memory::dims dst_dims_tf_order, dst_dims_mkl_order; // Get shapes of input tensors in MKL-DNN order MklDnnConvUtil conv_utl(context, strides_, padding_, data_format_, - dilations_); + dilations_); auto src_tf_shape = GetTfShape(context, kInputIndex_Src); auto filter_tf_shape = GetTfShape(context, kInputIndex_Filter); 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); + src_tf_shape, filter_tf_shape, &src_dims, &filter_dims, &strides, + &dilations, &dst_dims_tf_order, &dst_dims_mkl_order, &padding_left, + &padding_right); if (!context->status().ok()) return; // Check for corner case - if there is nothing to compute, return. @@ -946,21 +945,19 @@ class MklConvOp : public OpKernel { // Corner cases: output with 0 elements and 0 batch size. Tensor* dst_tensor = nullptr; - if (dst_tf_shape.num_elements() == 0 || - dst_dims_tf_order[0] == 0) { + if (dst_tf_shape.num_elements() == 0 || dst_dims_tf_order[0] == 0) { MklDnnShape dst_mkl_shape; dst_mkl_shape.SetMklTensor(false); - AllocateOutputSetMklShape(context, kOutputIndex_Dst, - &dst_tensor, src_tf_shape, dst_mkl_shape); + AllocateOutputSetMklShape(context, kOutputIndex_Dst, &dst_tensor, + src_tf_shape, dst_mkl_shape); // MklConv2D/3D also outputs converted filter // as 2nd output of Conv2D/3D. 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)) { + if (typeid(Tinput) == typeid(float)&&typeid(Tfilter) == + typeid(float)&&typeid(Toutput) == typeid(float)) { filter_mkl_shape.SetMklTensor(false); AllocateOutputSetMklShape(context, kOutputIndex_Filter, &output_filter_tensor, filter_tf_shape, @@ -1005,10 +1002,11 @@ class MklConvOp : public OpKernel { // in the following cases // 1. Legacy CPU without AVX512/AVX2, or // 2. 1x1 convolution with stride != 1 - bool do_not_cache = MklPrimitiveFactory::IsPrimitiveMemOptEnabled() && - (src_dims[MklDnnDims::Dim_N] > kSmallBatchSize) && - (MklPrimitiveFactory::IsLegacyPlatform() || - IsConv1x1StrideNot1(filter_dims, strides)); + 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 MklConvFwdPrimitive* @@ -1043,11 +1041,11 @@ class MklConvOp : public OpKernel { // 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); + AllocateOutputTensor(context, *conv_fwd_pd, dst_dims_mkl_order, tf_fmt, + &dst_tensor); Tensor* filter_out_tensor = nullptr; - if (typeid(Tinput) == typeid(float) && typeid(Tfilter) == typeid(float) && - typeid(Toutput) == typeid(float)) { + if (typeid(Tinput) == typeid(float)&&typeid(Tfilter) == + typeid(float)&&typeid(Toutput) == typeid(float)) { AllocateFilterOutputTensor(context, *conv_fwd_pd, TFShapeToMklDnnDims(filter_tf_shape), &filter_out_tensor); @@ -1071,10 +1069,11 @@ class MklConvOp : public OpKernel { filter.SetUsrMem(filter_md, &filter_tensor); filter.CheckReorderToOpMem(conv_fwd_pd.get()->weights_primitive_desc(), filter.GetTensorBuffer(filter_out_tensor)); - filter_data = static_cast(filter.GetOpMem().get_data_handle()); - } else { filter_data = - static_cast(const_cast(filter_tensor.flat().data())); + static_cast(filter.GetOpMem().get_data_handle()); + } else { + filter_data = static_cast( + const_cast(filter_tensor.flat().data())); } // execute convolution @@ -1089,12 +1088,14 @@ class MklConvOp : public OpKernel { // delete primitive since it is not cached. if (do_not_cache) delete conv_fwd; - } catch (mkldnn::error &e) { + } + catch (mkldnn::error& e) { string error_msg = tensorflow::strings::StrCat( "Status: ", e.status, ", message: ", string(e.message), ", in file ", __FILE__, ":", __LINE__); - OP_REQUIRES_OK(context, - errors::Aborted("Operation received an exception:", error_msg)); + OP_REQUIRES_OK( + context, + errors::Aborted("Operation received an exception:", error_msg)); } } @@ -1784,32 +1785,31 @@ REGISTER_KERNEL_BUILDER( #endif // INTEL_MKL_ML // Register 2D operations -#define REGISTER_MKL_CPU_2D(T) \ - REGISTER_KERNEL_BUILDER(Name("_MklConv2D") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvOp); \ - REGISTER_KERNEL_BUILDER(Name("_MklConv2DWithBias") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvOp); \ - REGISTER_KERNEL_BUILDER(Name("__MklDummyConv2DWithBias") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ +#define REGISTER_MKL_CPU_2D(T) \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklConv2D").Device(DEVICE_CPU).TypeConstraint("T").Label( \ + mkl_op_registry::kMklOpLabel), \ + MklConvOp); \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklConv2DWithBias") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvOp); \ + REGISTER_KERNEL_BUILDER(Name("__MklDummyConv2DWithBias") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ MklDummyOp); TF_CALL_float(REGISTER_MKL_CPU_2D); // Register 3D operations -#define REGISTER_MKL_CPU_3D(T) \ - REGISTER_KERNEL_BUILDER(Name("_MklConv3D") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvOp); +#define REGISTER_MKL_CPU_3D(T) \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklConv3D").Device(DEVICE_CPU).TypeConstraint("T").Label( \ + mkl_op_registry::kMklOpLabel), \ + MklConvOp); TF_CALL_float(REGISTER_MKL_CPU_3D); } // namespace tensorflow -- GitLab From d6cbdf31bb69a799e166cc6c43af0f5570d94fb1 Mon Sep 17 00:00:00 2001 From: mdfaijul Date: Wed, 3 Oct 2018 11:59:35 -0700 Subject: [PATCH 0044/1825] Removed tensorflow/tools/quantization/quantize_graph.py --- .../tools/quantization/quantize_graph.py | 1625 ----------------- 1 file changed, 1625 deletions(-) delete mode 100644 tensorflow/tools/quantization/quantize_graph.py diff --git a/tensorflow/tools/quantization/quantize_graph.py b/tensorflow/tools/quantization/quantize_graph.py deleted file mode 100644 index 14b572c15f..0000000000 --- a/tensorflow/tools/quantization/quantize_graph.py +++ /dev/null @@ -1,1625 +0,0 @@ -# Copyright 2015 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -r"""Transforms a float-trained graph into an equivalent quantized version. - -An example of command-line usage is: -bazel build tensorflow/tools/quantization:quantize_graph \ -&& bazel-bin/tensorflow/tools/quantization/quantize_graph \ ---input=tensorflow_inception_graph.pb ---output_node_names="softmax2" --print_nodes --output=/tmp/quantized_graph.pb \ ---mode=eightbit --logtostderr - -To quantize for Intel CPU, add --intel_cpu_eightbitize=True. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import collections -import re -import numpy as np - -from tensorflow.core.framework import attr_value_pb2 -from tensorflow.core.framework import graph_pb2 -from tensorflow.core.framework import node_def_pb2 -from tensorflow.python.client import session -from tensorflow.python.framework import constant_op -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import graph_util -from tensorflow.python.framework import importer -from tensorflow.python.framework import ops -from tensorflow.python.framework import tensor_shape -from tensorflow.python.framework import tensor_util -from tensorflow.python.ops import array_ops -from tensorflow.python.platform import app -from tensorflow.python.platform import flags as flags_lib -from tensorflow.python.platform import gfile -from google.protobuf import text_format - -flags = flags_lib -FLAGS = flags.FLAGS - -flags.DEFINE_boolean("print_nodes", False, """Lists all nodes in the model.""") -flags.DEFINE_string("input", "", """TensorFlow 'GraphDef' file to load.""") -flags.DEFINE_string("output_node_names", "", - """Output node names, comma separated.""") -flags.DEFINE_string("output", "", """File to save the output graph to.""") -flags.DEFINE_integer("bitdepth", 8, - """How many bits to quantize the graph to.""") -flags.DEFINE_string("mode", "round", - """What transformation to apply (round, quantize,""" - """ eightbit, weights, or weights_rounded).""") -flags.DEFINE_string("test_input_dims", "1,224,224,3", - """The size of the input tensor to use when testing a""" - """ graph loaded from a file.""") -flags.DEFINE_boolean("strip_redundant_quantization", True, - """Removes redundant dequantize/quantize pairs.""") -flags.DEFINE_boolean("quantized_input", False, - "If true, assume Placeholders are quantized with values " - "covering [--quantized_input_min,--quantized_input_max]. " - "Only supported when --mode=eightbit") -flags.DEFINE_float("quantized_input_min", 0, - "The minimum of the actual input range when " - "--quantized_input") -flags.DEFINE_float("quantized_input_max", 1, - "The maximum of the actual input range when " - "--quantized_input") -flags.DEFINE_float( - "quantized_fallback_min", None, - "The fallback 'min' value to use for layers which lack min-max " - "information. Note: this should be considered a coarse tool just good " - "enough for experimentation purposes, since graphs quantized in this way " - "would be very inaccurate.") -flags.DEFINE_float( - "quantized_fallback_max", None, - "The fallback 'max' value to use for layers which lack min-max " - "information. Note: this should be considered a coarse tool just good " - "enough for experimentation purposes, since graphs quantized in this way " - "would be very inaccurate.") -flags.DEFINE_boolean("input_binary", True, - """Input graph binary or text.""") -flags.DEFINE_boolean("output_binary", True, - """Output graph binary or text.""") -flags.DEFINE_boolean( - "intel_cpu_eightbitize", False, - "If true eightbitized graph will include fused quantized" - "nodes in the output_graph for Intel CPU.") - -def print_input_nodes(current_node, nodes_map, indent, already_visited): - print(" " * indent + current_node.op + ":" + current_node.name) - already_visited[current_node.name] = True - for input_node_name in current_node.input: - if input_node_name in already_visited: - continue - input_node = nodes_map[input_node_name] - print_input_nodes(input_node, nodes_map, indent + 1, already_visited) - - -def create_node(op, name, inputs): - new_node = node_def_pb2.NodeDef() - new_node.op = op - new_node.name = name - for input_name in inputs: - new_node.input.extend([input_name]) - return new_node - - -def create_constant_node(name, value, dtype, shape=None): - node = create_node("Const", name, []) - set_attr_dtype(node, "dtype", dtype) - set_attr_tensor(node, "value", value, dtype, shape) - return node - - -def copy_attr(node, key, attr_value): - try: - node.attr[key].CopyFrom(attr_value) - except KeyError: - pass - - -def set_attr_dtype(node, key, value): - try: - node.attr[key].CopyFrom( - attr_value_pb2.AttrValue(type=value.as_datatype_enum)) - except KeyError: - pass - - -def set_attr_shape(node, key, value): - try: - node.attr[key].CopyFrom( - attr_value_pb2.AttrValue(shape=tensor_shape.as_shape(value).as_proto())) - except KeyError: - pass - - -def set_attr_tensor(node, key, value, dtype, shape=None): - try: - node.attr[key].CopyFrom( - attr_value_pb2.AttrValue(tensor=tensor_util.make_tensor_proto( - value, dtype=dtype, shape=shape))) - except KeyError: - pass - - -def set_attr_string(node, key, value): - try: - node.attr[key].CopyFrom(attr_value_pb2.AttrValue(s=value)) - except KeyError: - pass - - -def set_attr_int_list(node, key, value): - list_value = attr_value_pb2.AttrValue.ListValue(i=value) - try: - node.attr[key].CopyFrom(attr_value_pb2.AttrValue(list=list_value)) - except KeyError: - pass - - -def set_attr_bool(node, key, value): - try: - node.attr[key].CopyFrom(attr_value_pb2.AttrValue(b=value)) - except KeyError: - pass - - -def set_attr_int(node, key, value): - try: - node.attr[key].CopyFrom(attr_value_pb2.AttrValue(i=value)) - except KeyError: - pass - - -def set_attr_float(node, key, value): - try: - node.attr[key].CopyFrom(attr_value_pb2.AttrValue(f=value)) - except KeyError: - pass - - -def node_name_from_input(node_name): - """Strips off ports and other decorations to get the underlying node name.""" - if node_name.startswith("^"): - node_name = node_name[1:] - m = re.search(r"(.*):\d+$", node_name) - if m: - node_name = m.group(1) - return node_name - - -def ensure_tensor_name_has_port(node_name): - """Makes sure that a tensor name has :0 if no explicit port exists.""" - m = re.search(r"(.*):\d+$", node_name) - if m: - name_with_port = node_name - else: - name_with_port = node_name + ":0" - return name_with_port - - -def unique_node_name_from_input(node_name): - """Replaces invalid characters in input names to get a unique node name.""" - return node_name.replace(":", "__port__").replace("^", "__hat__") - - -def quantize_array(arr, num_buckets): - """Quantizes a numpy array. - - This function maps each scalar in arr to the center of one of num_buckets - buckets. For instance, - quantize_array([0, 0.3, 0.6, 1], 2) => [0.25, 0.25, 0.75, 0.75] - - Args: - arr: The numpy array to quantize. - num_buckets: The number of buckets to map "var" to. - Returns: - The quantized numpy array. - Raises: - ValueError: when num_buckets < 1. - """ - if num_buckets < 1: - raise ValueError("num_buckets must be >= 1") - arr_max = arr.max() - arr_min = arr.min() - if arr_max == arr_min: - return arr - bucket_width = (arr_max - arr_min) / num_buckets - # Map scalars to bucket indices. Take special care of max(arr). - bucket_indices = np.floor((arr - arr_min) / bucket_width) - bucket_indices[bucket_indices == num_buckets] = num_buckets - 1 - # Map each scalar to the center of a bucket. - arr = arr_min + bucket_width * (bucket_indices + 0.5) - return arr - - -def quantize_weight_rounded(input_node): - """Returns a replacement node for input_node containing bucketed floats.""" - input_tensor = input_node.attr["value"].tensor - tensor_value = tensor_util.MakeNdarray(input_tensor) - shape = input_tensor.tensor_shape - # Currently, the parameter FLAGS.bitdepth is used to compute the - # number of buckets as 1 << FLAGS.bitdepth, meaning the number of - # buckets can only be a power of 2. - # This could be fixed by introducing a new parameter, num_buckets, - # which would allow for more flexibility in chosing the right model - # size/accuracy tradeoff. But I didn't want to add more parameters - # to this script than absolutely necessary. - num_buckets = 1 << FLAGS.bitdepth - tensor_value_rounded = quantize_array(tensor_value, num_buckets) - tensor_shape_list = tensor_util.TensorShapeProtoToList(shape) - return [ - create_constant_node( - input_node.name, - tensor_value_rounded, - dtypes.float32, - shape=tensor_shape_list) - ] - - -def quantize_weight_eightbit(input_node, quantization_mode): - """Returns replacement nodes for input_node using the Dequantize op.""" - base_name = input_node.name + "_" - quint8_const_name = base_name + "quint8_const" - min_name = base_name + "min" - max_name = base_name + "max" - float_tensor = tensor_util.MakeNdarray(input_node.attr["value"].tensor) - min_value = np.min(float_tensor.flatten()) - max_value = np.max(float_tensor.flatten()) - # Make sure that the range includes zero. - if min_value > 0.0: - min_value = 0.0 - # min_value == max_value is a tricky case. It can occur for general - # tensors, and of course for scalars. The quantized ops cannot deal - # with this case, so we set max_value to something else. - # It's a tricky question what is the numerically best solution to - # deal with this degeneracy. - # TODO(petewarden): Better use a tolerance than a hard comparison? - if min_value == max_value: - if abs(min_value) < 0.000001: - max_value = min_value + 1.0 - elif min_value > 0: - max_value = 2 * min_value - else: - max_value = min_value / 2.0 - - sess = session.Session() - with sess.as_default(): - quantize_op = array_ops.quantize_v2( - float_tensor, - min_value, - max_value, - dtypes.quint8, - mode=quantization_mode) - quint8_tensor = quantize_op[0].eval() - min_value = quantize_op[1].eval() - max_value = quantize_op[2].eval() - shape = tensor_util.TensorShapeProtoToList(input_node.attr["value"] - .tensor.tensor_shape) - quint8_const_node = create_constant_node( - quint8_const_name, quint8_tensor, dtypes.quint8, shape=shape) - min_node = create_constant_node(min_name, min_value, dtypes.float32) - max_node = create_constant_node(max_name, max_value, dtypes.float32) - dequantize_node = create_node("Dequantize", input_node.name, - [quint8_const_name, min_name, max_name]) - set_attr_dtype(dequantize_node, "T", dtypes.quint8) - set_attr_string(dequantize_node, "mode", quantization_mode) - return [quint8_const_node, min_node, max_node, dequantize_node] - -# TODO(intel-tf): Current Intel-CPU quantized Conv2D and Matmul supports only -# signed scaled mode of weight quantization. -def intel_cpu_quantize_weight_eightbit(input_node, quantization_mode="SCALED"): - """Returns replacement of constant weight node. - - This function creates (i) a quantized constant node, (ii) a float min node - (iii) a float max node, and (iv) a dequantize node.""" - base_name = input_node.name + "_" - qint8_const_name = base_name + "qint8_const" - min_name = base_name + "min" - max_name = base_name + "max" - float_tensor = tensor_util.MakeNdarray(input_node.attr["value"].tensor) - min_value = np.min(float_tensor.flatten()) - max_value = np.max(float_tensor.flatten()) - # Same processing of min-max as in quantize_weight_eightbit function. - if min_value > 0.0: - min_value = 0.0 - if min_value == max_value: - if abs(min_value) < 0.000001: - max_value = min_value + 1.0 - elif min_value > 0: - max_value = 2 * min_value - else: - max_value = min_value / 2.0 - - sess = session.Session() - with sess.as_default(): - quantize_op = array_ops.quantize_v2( - float_tensor, - min_value, - max_value, - dtypes.qint8, - mode=quantization_mode, - round_mode="HALF_TO_EVEN") - qint8_tensor = quantize_op[0].eval() - # Updated min-max values should be passed to the next feeding node. - min_value = quantize_op[1].eval() - max_value = quantize_op[2].eval() - shape = tensor_util.TensorShapeProtoToList(input_node.attr["value"] - .tensor.tensor_shape) - qint8_const_node = create_constant_node( - qint8_const_name, qint8_tensor, - dtypes.qint8, - shape=shape) - min_node = create_constant_node(min_name, min_value, dtypes.float32) - max_node = create_constant_node(max_name, max_value, dtypes.float32) - dequantize_node = create_node("Dequantize", input_node.name, - [qint8_const_name, min_name, max_name]) - set_attr_dtype(dequantize_node, "T", dtypes.qint8) - set_attr_string(dequantize_node, "mode", b'SCALED') - return [qint8_const_node, min_node, max_node, dequantize_node] - -EightbitizeRecursionState = collections.namedtuple( - "EightbitizeRecursionState", - ["already_visited", "output_node_stack", "merged_with_fake_quant"]) - - -class GraphRewriter(object): - """Takes a float graph, and rewrites it in quantized form.""" - - def __init__(self, - input_graph, - mode, - quantized_input_range, - fallback_quantization_range=None, - intel_cpu_eightbitize=False): - """Sets up the class to rewrite a float graph. - - Args: - input_graph: A float graph to transform. - mode: A string controlling how quantization is performed - - round, quantize, eightbit, or weights. - quantized_input_range: if set, assume the input is - quantized and represents the range - [quantized_input_range[0], quantized_input_range[1]] - fallback_quantization_range: if set, then for nodes where the quantization - range can't be inferred from the graph, use the range - [fallback_quantization_range[0], fallback_quantization_range[1]) instead - of using a RequantizationRange node in the graph. - - Raises: - ValueError: Two nodes with the same name were found in the graph. - """ - self.input_graph = input_graph - self.nodes_map = self.create_nodes_map(input_graph) - self.output_graph = None - self.mode = mode - self.intel_cpu_eightbitize = intel_cpu_eightbitize - self.final_node_renames = {} - if quantized_input_range: - self.input_range = (quantized_input_range[0], quantized_input_range[1]) - if self.input_range[0] >= self.input_range[1]: - raise ValueError("Invalid quantized_input_range: [%s,%s]" % - self.input_range) - if self.mode != "eightbit": - raise ValueError( - "quantized_input_range can only be specified in eightbit mode") - else: - self.input_range = None - - if fallback_quantization_range: - self.fallback_quantization_range = [ - fallback_quantization_range[0], fallback_quantization_range[1] - ] - if (self.fallback_quantization_range[0] >= - self.fallback_quantization_range[1]): - raise ValueError("Invalid fallback_quantization_range: [%s,%s]" % - self.fallback_quantization_range) - if self.mode != "eightbit": - raise ValueError("fallback_quantization_range can only be " - "specified in eightbit mode") - else: - self.fallback_quantization_range = None - - # Data that is valid only during the recursive call to rewrite the graph. - self.state = None - - def create_nodes_map(self, graph): - """Builds a mapping of node names to their defs from the graph.""" - nodes_map = {} - for node in graph.node: - if node.name not in nodes_map.keys(): - nodes_map[node.name] = node - else: - raise ValueError("Duplicate node names detected.") - return nodes_map - - def rewrite(self, output_node_names): - """Triggers rewriting of the float graph. - - Args: - output_node_names: A list of names of the nodes that produce the final - results. - - Returns: - A quantized version of the float graph. - """ - self.output_graph = graph_pb2.GraphDef() - output_nodes = [ - self.nodes_map[output_node_name] - for output_node_name in output_node_names - ] - if self.mode == "round": - self.already_visited = {} - for output_node in output_nodes: - self.round_nodes_recursively(output_node) - elif self.mode == "quantize": - self.already_visited = {} - self.already_quantized = {} - for output_node in output_nodes: - self.quantize_nodes_recursively(output_node) - elif self.mode == "eightbit": - self.set_input_graph(graph_util.remove_training_nodes( - self.input_graph, protected_nodes=output_node_names)) - output_nodes = [ - self.nodes_map[output_node_name] - for output_node_name in output_node_names - ] - - self.state = EightbitizeRecursionState( - already_visited={}, output_node_stack=[], merged_with_fake_quant={}) - - if self.intel_cpu_eightbitize: - # TODO(intel-tf): Enables fused quantized node for intel cpu. - for output_node in output_nodes: - # Intiailize output_node_stack with output node. - # Each element in the stack is a mutable list containing - # [parent_node, index_to_parent, quantization_flag, fusion_flag]. - # In case of root node, make self as parent. - self.state.output_node_stack.append( - [output_node, None, False, False]) - self.intel_cpu_eightbitize_nodes_recursively(output_node) - self.state.output_node_stack.pop() - else: - for output_node in output_nodes: - self.eightbitize_nodes_recursively(output_node) - - self.state = None - if self.input_range: - self.add_output_graph_node( - create_constant_node("quantized_input_min_value", self.input_range[ - 0], dtypes.float32, [])) - self.add_output_graph_node( - create_constant_node("quantized_input_max_value", self.input_range[ - 1], dtypes.float32, [])) - if self.fallback_quantization_range: - self.add_output_graph_node( - create_constant_node("fallback_quantization_min_value", - self.fallback_quantization_range[0], - dtypes.float32, [])) - self.add_output_graph_node( - create_constant_node("fallback_quantization_max_value", - self.fallback_quantization_range[1], - dtypes.float32, [])) - if FLAGS.strip_redundant_quantization: - self.output_graph = self.remove_redundant_quantization( - self.output_graph) - self.remove_dead_nodes(output_node_names) - self.apply_final_node_renames() - elif self.mode == "weights": - self.output_graph = self.quantize_weights(self.input_graph, - b"MIN_COMBINED") - self.remove_dead_nodes(output_node_names) - elif self.mode == "weights_rounded": - self.output_graph = self.quantize_weights(self.input_graph, self.mode) - self.remove_dead_nodes(output_node_names) - else: - print("Bad mode - " + self.mode + ".") - return self.output_graph - - def round_nodes_recursively(self, current_node): - """The entry point for simple rounding quantization.""" - if (current_node.name in self.already_visited - ) and self.already_visited[current_node.name]: - return - self.already_visited[current_node.name] = True - for input_node_name in current_node.input: - input_node_name = node_name_from_input(input_node_name) - input_node = self.nodes_map[input_node_name] - self.round_nodes_recursively(input_node) - nodes_to_quantize = ["Conv2D", "BiasAdd", "MatMul"] - if any(current_node.op in s for s in nodes_to_quantize): - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(current_node) - new_node.name = current_node.name + "_original" - self.add_output_graph_node(new_node) - levels = 1 << FLAGS.bitdepth - constant_name = current_node.name + "_round_depth" - constant_tensor = constant_op.constant( - levels, dtype=dtypes.int32, name=constant_name) - constant_node = constant_tensor.op.node_def - self.add_output_graph_node(constant_node) - quantize_node = node_def_pb2.NodeDef() - quantize_node.op = "RoundToSteps" - quantize_node.name = current_node.name - quantize_node.input.extend([current_node.name + "_original"]) - quantize_node.input.extend([constant_node.name]) - self.add_output_graph_node(quantize_node) - else: - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(current_node) - self.add_output_graph_node(new_node) - - def quantize_nodes_recursively(self, current_node): - """The entry point for quantizing nodes to eight bit and back.""" - if self.already_visited[current_node.name]: - return - self.already_visited[current_node.name] = True - for input_node_name in current_node.input: - input_node_name = node_name_from_input(input_node_name) - input_node = self.nodes_map[input_node_name] - self.quantize_nodes_recursively(input_node) - nodes_to_quantize = ["Conv2D", "BiasAdd", "MatMul"] - if any(current_node.op in s for s in nodes_to_quantize): - for input_name in current_node.input: - input_name = node_name_from_input(input_name) - input_node = self.nodes_map[input_name] - self.quantize_node(input_node) - self.quantize_node(current_node) - else: - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(current_node) - self.add_output_graph_node(new_node) - - def quantize_node(self, input_node): - """Handles quantizing a single node.""" - input_name = input_node.name - if input_name in self.already_quantized: - return - self.already_quantized[input_name] = True - original_input_name = input_name + "_original" - reshape_name = input_name + "_reshape" - reshape_dims_name = input_name + "_reshape_dims" - max_name = input_name + "_max" - min_name = input_name + "_min" - dims_name = input_name + "_dims" - quantize_name = input_name + "_quantize" - dequantize_name = input_name - original_input_node = node_def_pb2.NodeDef() - original_input_node.CopyFrom(input_node) - original_input_node.name = original_input_name - self.add_output_graph_node(original_input_node) - reshape_dims_node = create_constant_node(reshape_dims_name, -1, - dtypes.int32, [1]) - self.add_output_graph_node(reshape_dims_node) - reshape_node = create_node("Reshape", reshape_name, - [original_input_name, reshape_dims_name]) - set_attr_dtype(reshape_node, "T", dtypes.float32) - self.add_output_graph_node(reshape_node) - dims_node = create_constant_node(dims_name, 0, dtypes.int32, [1]) - self.add_output_graph_node(dims_node) - max_node = create_node("Max", max_name, [reshape_name, dims_name]) - set_attr_dtype(max_node, "T", dtypes.float32) - set_attr_bool(max_node, "keep_dims", False) - self.add_output_graph_node(max_node) - min_node = create_node("Min", min_name, [reshape_name, dims_name]) - set_attr_dtype(min_node, "T", dtypes.float32) - set_attr_bool(min_node, "keep_dims", False) - self.add_output_graph_node(min_node) - quantize_node = create_node("Quantize", quantize_name, - [original_input_name, min_name, max_name]) - set_attr_dtype(quantize_node, "T", dtypes.quint8) - set_attr_string(quantize_node, "mode", b"MIN_FIRST") - self.add_output_graph_node(quantize_node) - dequantize_node = create_node("Dequantize", dequantize_name, - [quantize_name, min_name, max_name]) - set_attr_dtype(dequantize_node, "T", dtypes.quint8) - set_attr_string(dequantize_node, "mode", b"MIN_FIRST") - self.add_output_graph_node(dequantize_node) - - def should_merge_with_fake_quant_node(self): - """Should the current node merge with self.state.output_node_stack[-1]?""" - if not self.state.output_node_stack: - return False - top = self.state.output_node_stack[-1] - return top[1] == 0 and top[0].op in ["FakeQuantWithMinMaxVars"] - - def should_quantize_const(self, node): - if not self.state.output_node_stack: - return False - top = self.state.output_node_stack[-1] - if not top[2]: - return False - dtype = dtypes.as_dtype(node.attr["dtype"].type) - assert dtype == dtypes.float32, ( - "Failed to quantized constant %s of type %s" % (node.name, dtype)) - return True - - def eightbitize_nodes_recursively(self, current_node): - """The entry point for transforming a graph into full eight bit.""" - if current_node.name in self.state.already_visited: - if (self.should_merge_with_fake_quant_node() or - current_node.name in self.state.merged_with_fake_quant): - raise ValueError("Unsupported graph structure: output of node %s " - "is processed by a FakeQuant* node and should have " - "no other outputs.", current_node.name) - return - self.state.already_visited[current_node.name] = True - - for i, input_node_name in enumerate(current_node.input): - quantize_input = False - if current_node.op in ("MatMul", "Conv2D", "BiasAdd", "MaxPool", - "AvgPool", "Relu", "Relu6", - "BatchNormWithGlobalNormalization"): - quantize_input = True - elif current_node.op == "Concat" and i > 0: - quantize_input = ( - dtypes.as_dtype(current_node.attr["T"].type) == dtypes.float32) - elif current_node.op == "Reshape" and i == 0: - quantize_input = ( - dtypes.as_dtype(current_node.attr["T"].type) == dtypes.float32) - - self.state.output_node_stack.append((current_node, i, quantize_input)) - - input_node_name = node_name_from_input(input_node_name) - input_node = self.nodes_map[input_node_name] - self.eightbitize_nodes_recursively(input_node) - - self.state.output_node_stack.pop() - - if current_node.op == "MatMul": - self.eightbitize_mat_mul_node(current_node) - elif current_node.op == "Conv2D": - self.eightbitize_conv_node(current_node) - elif current_node.op == "BiasAdd": - self.eightbitize_bias_add_node(current_node) - elif current_node.op == "MaxPool" or current_node.op == "AvgPool": - self.eightbitize_single_input_tensor_node(current_node, - self.add_pool_function) - elif current_node.op == "Relu" or current_node.op == "Relu6": - self.eightbitize_single_input_tensor_node(current_node, - self.add_relu_function) - elif (current_node.op == "Concat" and - dtypes.as_dtype(current_node.attr["T"].type) == dtypes.float32): - self.eightbitize_concat_node(current_node) - elif current_node.op == "BatchNormWithGlobalNormalization": - self.eightbitize_batch_norm_node(current_node) - elif (current_node.op == "Reshape" and - dtypes.as_dtype(current_node.attr["T"].type) == dtypes.float32): - self.eightbitize_reshape_node(current_node) - elif (self.input_range and - current_node.op in ("Placeholder", "PlaceholderV2")): - self.eightbitize_placeholder_node(current_node) - elif current_node.op == "FakeQuantWithMinMaxVars": - # It will have been merged into the underlying node. - pass - elif current_node.op == "Const": - if self.should_quantize_const(current_node): - for n in quantize_weight_eightbit(current_node, b"MIN_FIRST"): - self.add_output_graph_node(n) - else: - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(current_node) - self.add_output_graph_node(new_node) - - ################################################################### - # Note: if more cases are added here, you may need to update the op - # name lists in the loop over children at the start of the function. - ################################################################### - else: - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(current_node) - self.add_output_graph_node(new_node) - - if (self.should_merge_with_fake_quant_node() and - current_node.name not in self.state.merged_with_fake_quant): - raise ValueError( - "FakeQuant* node %s failed to merge with node %s of type %s" % - (self.state.output_node_stack[-1][0], current_node.name, - current_node.op)) - - # TODO(intel-tf): Quantized Conv2D could be fused with few other succeeding - # ops. Current support is for BiasAdd and Relu. Future implementation will - # include: - # (i) Conv2D + {BiasAdd} + Relu + Add + Relu - # (ii) Conv2D + {BiasAdd} + Relu + Add - # (ii) Conv2D + {BiasAdd} + Add + Relu - # (iii) Conv2D + {BiasAdd} + Add - def intel_cpu_eightbitize_conv_node(self, original_node, bias_node=None, - bias_add_name=None, add_node_name=None, - relu_node_name=None): - """Replaces a Conv2D node with the eight bit equivalent sub-graph.""" - all_input_names = self.add_eightbit_prologue_nodes(original_node) - - if bias_node and add_node_name and relu_node_name: - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(bias_node) - self.add_output_graph_node(new_node) - all_input_names = all_input_names[:2] + [bias_node.name] + \ - all_input_names[2:] + [add_node_name] - quantized_conv_name = original_node.name + "_eightbit_quantized_conv" - quantized_conv_node = create_node("QuantizedConv2DWithBiasSumAndRelu", - quantized_conv_name, all_input_names) - elif bias_node and (not add_node_name) and relu_node_name: - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(bias_node) - self.add_output_graph_node(new_node) - all_input_names = all_input_names[:2] + [bias_node.name] + \ - all_input_names[2:] - quantized_conv_name = original_node.name + "_eightbit_quantized_conv" - quantized_conv_node = create_node("QuantizedConv2DWithBiasAndRelu", - quantized_conv_name, all_input_names) - elif bias_node and bias_add_name and \ - (not add_node_name) and (not relu_node_name): - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(bias_node) - self.add_output_graph_node(new_node) - all_input_names = all_input_names[:2] + [bias_node.name] + \ - all_input_names[2:] - quantized_conv_name = original_node.name + "_eightbit_quantized_conv" - quantized_conv_node = create_node("QuantizedConv2DWithBias", - quantized_conv_name, all_input_names) - else: - quantized_conv_name = original_node.name + "_eightbit_quantized_conv" - quantized_conv_node = create_node("QuantizedConv2D", quantized_conv_name, - all_input_names) - copy_attr(quantized_conv_node, "strides", original_node.attr["strides"]) - copy_attr(quantized_conv_node, "padding", original_node.attr["padding"]) - copy_attr(quantized_conv_node, "dilations", original_node.attr["dilations"]) - set_attr_dtype(quantized_conv_node, "Tinput", dtypes.quint8) - set_attr_dtype(quantized_conv_node, "Tfilter", dtypes.qint8) - set_attr_dtype(quantized_conv_node, "out_type", dtypes.qint32) - self.add_output_graph_node(quantized_conv_node) - quantize_down_name = self.add_quantize_down_nodes(original_node, - quantized_conv_name) - if bias_node and relu_node_name: - self.add_dequantize_result_node(quantize_down_name, relu_node_name) - elif bias_node and bias_add_name and \ - (not add_node_name) and (not relu_node_name): - self.add_dequantize_result_node(quantize_down_name, bias_add_name) - else: - self.add_dequantize_result_node(quantize_down_name, original_node.name) - - # TODO(intel-tf): To check whether Conv2D is fed by relu directly or via - # pooling ops. This is required as intel cpu requires input tensor for Conv2D - # to be non-negative. - def intel_cpu_find_relu_recursively(self, current_node): - """Helper function to check if Conv2D is fed by Relu.""" - if current_node.op == "Relu": - return True - else: - first_input_node_name = node_name_from_input(current_node.input[0]) - input_node = self.nodes_map[first_input_node_name] - if input_node.op in ("ConcatV2", "MaxPool", "AvgPool", "Relu"): - return self.intel_cpu_find_relu_recursively(input_node) - else: - return False - - # TODO(intel-tf): We leave the output graph partially quantized for - # intel cpu. Current quantization support is for Conv2D and its fusion. - # More quantized operations will be included as more implementations are - # completed. - def intel_cpu_eightbitize_nodes_recursively(self, current_node): - """The entry point for transforming a graph into full eight bit.""" - if current_node.name in self.state.already_visited: - if (self.should_merge_with_fake_quant_node() or - current_node.name in self.state.merged_with_fake_quant): - raise ValueError("Unsupported graph structure: output of node %s " - "is processed by a FakeQuant* node and should have " - "no other outputs.", current_node.name) - return - - self.state.already_visited[current_node.name] = True - quantize_input, should_quantize_conv, \ - fuse_with_conv = (False, False, False) - - if current_node.op == "Conv2D": - should_quantize_conv = self.intel_cpu_find_relu_recursively(current_node) - - inputs = list(enumerate(current_node.input)) - if current_node.op == "AddN": - inputs = reversed(inputs) # pylint: disable=redefined-variable-type - - for i, input_node_name in inputs: - input_node_name = node_name_from_input(input_node_name) - input_node = self.nodes_map[input_node_name] - - if should_quantize_conv and i == 1 and input_node.op == "Const": - quantize_input = True - - self.state.output_node_stack.append([current_node, i, quantize_input, - fuse_with_conv]) - self.intel_cpu_eightbitize_nodes_recursively(input_node) - self.state.output_node_stack.pop() - - if current_node.op == "Conv2D" and should_quantize_conv and quantize_input: - # match pattern for fusion with bias and relu - grand_parent, parent = self.state.output_node_stack[-2:] - if parent[0].op == "BiasAdd" and grand_parent[0].op == "Relu": - self.state.output_node_stack[-2][3] = True # BiasAdd to be fused - self.state.output_node_stack[-3][3] = True # Relu to be fused - bias_node_name = node_name_from_input(parent[0].input[1]) - bias_node = self.nodes_map[bias_node_name] - self.intel_cpu_eightbitize_conv_node(current_node, bias_node, None, - None, grand_parent[0].name) - elif parent[0].op == "BiasAdd" and grand_parent[0].op == "AddN": - grand_grand_parent = self.state.output_node_stack[-3] - if grand_grand_parent[0].op == "Relu" \ - and (not self.state.output_node_stack[-3][3]) \ - and (not self.state.output_node_stack[-4][3]): - self.state.output_node_stack[-2][3] = True # BiasAdd to be fused - self.state.output_node_stack[-3][3] = True # AddN to be fused - self.state.output_node_stack[-4][3] = True # Relu to be fused - bias_node_name = node_name_from_input(parent[0].input[1]) - bias_node = self.nodes_map[bias_node_name] - add_node_name = node_name_from_input(grand_parent[0].input[0]) - self.intel_cpu_eightbitize_conv_node(current_node, bias_node, None, - add_node_name, - grand_grand_parent[0].name) - elif not self.state.output_node_stack[-2][3]: # Fuse BiasAdd then - self.state.output_node_stack[-2][3] = True # BiasAdd to be fused - bias_node_name = node_name_from_input(parent[0].input[1]) - bias_node = self.nodes_map[bias_node_name] - self.intel_cpu_eightbitize_conv_node(current_node, bias_node, - parent[0].name) - else: - self.intel_cpu_eightbitize_conv_node(current_node) - elif parent[0].op == "BiasAdd" and \ - (not self.state.output_node_stack[-2][3]): - self.state.output_node_stack[-2][3] = True # BiasAdd to be fused - bias_node_name = node_name_from_input(parent[0].input[1]) - bias_node = self.nodes_map[bias_node_name] - self.intel_cpu_eightbitize_conv_node(current_node, bias_node, - parent[0].name) - else: - self.intel_cpu_eightbitize_conv_node(current_node) - elif current_node.op == "BiasAdd" and \ - self.state.output_node_stack[-1][3]: - pass # This op is already processed by fused quantization - elif current_node.op == "Relu" and \ - self.state.output_node_stack[-1][3]: - pass # This op is already processed by fused quantization - elif current_node.op == "AddN" and \ - self.state.output_node_stack[-1][3]: - pass # AddN op is already processed by fused quatization - elif current_node.op == "MaxPool" or current_node.op == "AvgPool": - self.eightbitize_single_input_tensor_node(current_node, - self.add_pool_function) - elif (current_node.op == "ConcatV2" and - dtypes.as_dtype(current_node.attr["T"].type) == dtypes.float32): - self.eightbitize_concatv2_node(current_node) - elif current_node.op == "Const": - parent = self.state.output_node_stack[-1] - if parent[0].op == "Conv2D" and parent[2]: - for n in intel_cpu_quantize_weight_eightbit(current_node, b"SCALED"): - self.add_output_graph_node(n) - elif parent[0].op == "BiasAdd" and \ - self.state.output_node_stack[-2][3]: - pass # This constant is already process by fused quantization - else: - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(current_node) - self.add_output_graph_node(new_node) - else: - new_node = node_def_pb2.NodeDef() - new_node.CopyFrom(current_node) - self.add_output_graph_node(new_node) - - if (self.should_merge_with_fake_quant_node() and - current_node.name not in self.state.merged_with_fake_quant): - raise ValueError( - "FakeQuant* node %s failed to merge with node %s of type %s" % - (self.state.output_node_stack[-1][0], current_node.name, - current_node.op)) - - def add_eightbit_prologue_nodes(self, original_node): - """Adds input conversion nodes to handle quantizing the underlying node.""" - namespace_prefix = original_node.name + "_eightbit" - reshape_dims_name, reduction_dims_name = self.add_common_quantization_nodes( - namespace_prefix) - input_names = [] - min_max_names = [] - for original_input_name in original_node.input: - quantize_input_name, min_input_name, max_input_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_input_name, - reshape_dims_name, - reduction_dims_name)) - input_names.append(quantize_input_name) - min_max_names.append(min_input_name) - min_max_names.append(max_input_name) - all_input_names = [] - all_input_names.extend(input_names) - all_input_names.extend(min_max_names) - return all_input_names - - def add_common_quantization_nodes(self, namespace_prefix): - """Builds constant nodes needed for quantization of inputs.""" - reshape_dims_name = namespace_prefix + "_reshape_dims" - reduction_dims_name = namespace_prefix + "_reduction_dims" - - reshape_dims_node = create_constant_node(reshape_dims_name, -1, - dtypes.int32, [1]) - self.add_output_graph_node(reshape_dims_node) - reduction_dims_node = create_constant_node(reduction_dims_name, 0, - dtypes.int32, [1]) - self.add_output_graph_node(reduction_dims_node) - return reshape_dims_name, reduction_dims_name - - def eightbitize_input_to_node(self, namespace_prefix, original_input_name, - reshape_dims_name, reduction_dims_name): - """Takes one float input to an op, and converts it to quantized form.""" - unique_input_name = unique_node_name_from_input(original_input_name) - reshape_input_name = namespace_prefix + "_reshape_" + unique_input_name - min_input_name = namespace_prefix + "_min_" + unique_input_name - max_input_name = namespace_prefix + "_max_" + unique_input_name - quantize_input_name = namespace_prefix + "_quantize_" + unique_input_name - reshape_input_node = create_node("Reshape", reshape_input_name, - [original_input_name, reshape_dims_name]) - set_attr_dtype(reshape_input_node, "T", dtypes.float32) - self.add_output_graph_node(reshape_input_node) - min_input_node = create_node("Min", min_input_name, - [reshape_input_name, reduction_dims_name]) - set_attr_dtype(min_input_node, "T", dtypes.float32) - set_attr_bool(min_input_node, "keep_dims", False) - self.add_output_graph_node(min_input_node) - max_input_node = create_node("Max", max_input_name, - [reshape_input_name, reduction_dims_name]) - set_attr_dtype(max_input_node, "T", dtypes.float32) - set_attr_bool(max_input_node, "keep_dims", False) - self.add_output_graph_node(max_input_node) - quantize_input_node = create_node( - "QuantizeV2", quantize_input_name, - [original_input_name, min_input_name, max_input_name]) - set_attr_dtype(quantize_input_node, "T", dtypes.quint8) - set_attr_string(quantize_input_node, "mode", - b"SCALED" if self.intel_cpu_eightbitize else b"MIN_FIRST") - set_attr_string(quantize_input_node, "round_mode", - b"HALF_TO_EVEN" if self.intel_cpu_eightbitize - else b"HALF_AWAY_FROM_ZERO") - self.add_output_graph_node(quantize_input_node) - min_output_name = quantize_input_name + ":1" - max_output_name = quantize_input_name + ":2" - return quantize_input_name, min_output_name, max_output_name - - def add_quantize_down_nodes(self, original_node, quantized_output_name): - quantized_outputs = [ - quantized_output_name, quantized_output_name + ":1", - quantized_output_name + ":2" - ] - min_max_inputs = None - if self.should_merge_with_fake_quant_node(): - # Use the inputs to the FakeQuantWithMinMaxVars node as the inputs to - # Requantize. - fake_quant_node = self.state.output_node_stack[-1][0] - min_max_inputs = [fake_quant_node.input[1], fake_quant_node.input[2]] - assert original_node.name not in self.state.merged_with_fake_quant - self.state.merged_with_fake_quant[original_node.name] = True - elif self.fallback_quantization_range: - min_max_inputs = [ - "fallback_quantization_min_value:0", - "fallback_quantization_max_value:0" - ] - else: - # Add a RequantizationRange node for finding the min and max values. - requant_range_node = create_node( - "RequantizationRange", original_node.name + "_eightbit_requant_range", - quantized_outputs) - set_attr_dtype(requant_range_node, "Tinput", dtypes.qint32) - self.add_output_graph_node(requant_range_node) - min_max_inputs = [ - requant_range_node.name + ":0", requant_range_node.name + ":1" - ] - requantize_node = create_node("Requantize", - original_node.name + "_eightbit_requantize", - quantized_outputs + min_max_inputs) - set_attr_dtype(requantize_node, "Tinput", dtypes.qint32) - set_attr_dtype(requantize_node, "out_type", dtypes.quint8) - self.add_output_graph_node(requantize_node) - return requantize_node.name - - def add_dequantize_result_node(self, - quantized_output_name, - original_node_name, - min_tensor_index=1): - min_max_inputs = [ - "%s:%s" % (quantized_output_name, min_tensor_index), - "%s:%s" % (quantized_output_name, (min_tensor_index + 1)) - ] - dequantize_name = original_node_name - if self.should_merge_with_fake_quant_node(): - fake_quant_node = self.state.output_node_stack[-1][0] - if original_node_name not in self.state.merged_with_fake_quant: - min_max_inputs = [fake_quant_node.input[1], fake_quant_node.input[2]] - self.state.merged_with_fake_quant[original_node_name] = True - dequantize_name = fake_quant_node.name - - dequantize_node = create_node( - "Dequantize", dequantize_name, - [quantized_output_name, min_max_inputs[0], min_max_inputs[1]]) - set_attr_dtype(dequantize_node, "T", dtypes.quint8) - set_attr_string(dequantize_node, "mode", b"MIN_FIRST") - self.add_output_graph_node(dequantize_node) - - def eightbitize_mat_mul_node(self, original_node): - """Replaces a MatMul node with the eight bit equivalent sub-graph.""" - quantized_mat_mul_name = original_node.name + "_eightbit_quantized_mat_mul" - all_input_names = self.add_eightbit_prologue_nodes(original_node) - quantized_mat_mul_node = create_node("QuantizedMatMul", - quantized_mat_mul_name, - all_input_names) - set_attr_dtype(quantized_mat_mul_node, "T1", dtypes.quint8) - set_attr_dtype(quantized_mat_mul_node, "T2", dtypes.quint8) - set_attr_dtype(quantized_mat_mul_node, "Toutput", dtypes.qint32) - copy_attr(quantized_mat_mul_node, "transpose_a", - original_node.attr["transpose_a"]) - copy_attr(quantized_mat_mul_node, "transpose_b", - original_node.attr["transpose_b"]) - self.add_output_graph_node(quantized_mat_mul_node) - quantize_down_name = self.add_quantize_down_nodes(original_node, - quantized_mat_mul_name) - self.add_dequantize_result_node(quantize_down_name, original_node.name) - - def eightbitize_conv_node(self, original_node): - """Replaces a Conv2D node with the eight bit equivalent sub-graph.""" - all_input_names = self.add_eightbit_prologue_nodes(original_node) - quantized_conv_name = original_node.name + "_eightbit_quantized_conv" - quantized_conv_node = create_node("QuantizedConv2D", quantized_conv_name, - all_input_names) - copy_attr(quantized_conv_node, "strides", original_node.attr["strides"]) - copy_attr(quantized_conv_node, "padding", original_node.attr["padding"]) - set_attr_dtype(quantized_conv_node, "Tinput", dtypes.quint8) - set_attr_dtype(quantized_conv_node, "Tfilter", dtypes.quint8) - set_attr_dtype(quantized_conv_node, "out_type", dtypes.qint32) - self.add_output_graph_node(quantized_conv_node) - quantize_down_name = self.add_quantize_down_nodes(original_node, - quantized_conv_name) - self.add_dequantize_result_node(quantize_down_name, original_node.name) - - def eightbitize_bias_add_node(self, original_node): - """Replaces a BiasAdd node with the eight bit equivalent sub-graph.""" - quantized_bias_add_name = ( - original_node.name + "_eightbit_quantized_bias_add") - all_input_names = self.add_eightbit_prologue_nodes(original_node) - quantized_bias_add_node = create_node("QuantizedBiasAdd", - quantized_bias_add_name, - all_input_names) - set_attr_dtype(quantized_bias_add_node, "T1", dtypes.quint8) - set_attr_dtype(quantized_bias_add_node, "T2", dtypes.quint8) - set_attr_dtype(quantized_bias_add_node, "out_type", dtypes.qint32) - self.add_output_graph_node(quantized_bias_add_node) - quantize_down_name = self.add_quantize_down_nodes(original_node, - quantized_bias_add_name) - self.add_dequantize_result_node(quantize_down_name, original_node.name) - - def eightbitize_single_input_tensor_node(self, original_node, - add_op_function): - """Replaces a single-tensor node with the eight bit equivalent sub-graph. - - Converts a node like this: - - Shape(f) Input(f) - | | - +--------v v - Operation - | - v - (f) - - Into a quantized equivalent: - - Input(f) ReshapeDims - +------v v-------------+ - | Reshape - | | - | | ReductionDims - | +-----+ | - | | +---c---------+ - | v v v v-------+ - | Min Max - | +----+ | - v v v--------+ - Quantize - | - v - QuantizedOperation - | | | - v v v - Dequantize - | - v - (f) - - - Args: - original_node: Float node to be converted. - add_op_function: Function to create the actual node. - - Returns: - Subgraph representing the quantized version of the original node. - - """ - quantized_op_name = original_node.name + "_eightbit_quantized" - quantized_op_type = "Quantized" + original_node.op - all_input_names = self.add_eightbit_prologue_nodes(original_node) - quantized_op_node = create_node(quantized_op_type, quantized_op_name, - all_input_names) - add_op_function(original_node, quantized_op_node) - self.add_output_graph_node(quantized_op_node) - self.add_dequantize_result_node(quantized_op_name, original_node.name) - - def add_pool_function(self, original_node, quantized_op_node): - set_attr_dtype(quantized_op_node, "T", dtypes.quint8) - copy_attr(quantized_op_node, "ksize", original_node.attr["ksize"]) - copy_attr(quantized_op_node, "strides", original_node.attr["strides"]) - copy_attr(quantized_op_node, "padding", original_node.attr["padding"]) - - def add_relu_function(self, unused_arg_node, quantized_op_node): - set_attr_dtype(quantized_op_node, "Tinput", dtypes.quint8) - - def eightbitize_concat_node(self, original_node): - """Replaces a Concat node with the eight bit equivalent sub-graph. - - Converts a node like this: - - Shape(f) Input0(f) Input1(f) - | | | - +--------v v v----------+ - Concat - | - v - (f) - - Into a quantized equivalent: - - Shape(f) Input0(f) ReshapeDims Input1(f) - | +------v v--------------+------------------v v------+ - | | Reshape Reshape | - | | | | | - | | | ReductionDims | | - | | +------+ | +--------+ | - | | | +---c---------+-----------c-----+ | | - | | +v v v v-------+---------v v v v+ | - | | Min Max Min Max | - | | +----+ | | +-----+ | - | v v v--------+ +----------v v v - | Quantize Quantize - | +------------------+ +----------------------+ - +-------------------------------+ | | - v v v - QuantizedConcat - | | | - v v v - Dequantize - | - v - (f) - Args: - original_node: Float node to be converted. - - Returns: - Subgraph representing the quantized version of the original node. - - """ - namespace_prefix = original_node.name + "_eightbit" - quantized_concat_name = namespace_prefix + "_quantized_concat" - reshape_dims_name, reduction_dims_name = self.add_common_quantization_nodes( - namespace_prefix) - shape_input_name = original_node.input[0] - original_inputs = original_node.input[1:] - input_names = [] - min_names = [] - max_names = [] - for original_input_name in original_inputs: - quantize_input_name, min_input_name, max_input_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_input_name, - reshape_dims_name, - reduction_dims_name)) - input_names.append(quantize_input_name) - min_names.append(min_input_name) - max_names.append(max_input_name) - all_input_names = [shape_input_name] - all_input_names.extend(input_names) - all_input_names.extend(min_names) - all_input_names.extend(max_names) - quantized_concat_node = create_node("QuantizedConcat", - quantized_concat_name, all_input_names) - set_attr_int(quantized_concat_node, "N", len(original_inputs)) - set_attr_dtype(quantized_concat_node, "T", dtypes.quint8) - self.add_output_graph_node(quantized_concat_node) - self.add_dequantize_result_node(quantized_concat_name, original_node.name) - - def eightbitize_concatv2_node(self, original_node): - """ - Args: - original_node: Float node to be converted. - - Returns: - Subgraph representing the quantized version of the original node. - - """ - namespace_prefix = original_node.name + "_eightbit" - quantized_concat_name = namespace_prefix + "_quantized_concatv2" - reshape_dims_name, reduction_dims_name = self.add_common_quantization_nodes( - namespace_prefix) - num_input = len(original_node.input) - shape_input_name = original_node.input[num_input-1] - original_inputs = original_node.input[0:num_input-1] - input_names = [] - min_names = [] - max_names = [] - for original_input_name in original_inputs: - quantize_input_name, min_input_name, max_input_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_input_name, - reshape_dims_name, - reduction_dims_name)) - input_names.append(quantize_input_name) - min_names.append(min_input_name) - max_names.append(max_input_name) - all_input_names = input_names - all_input_names.append(shape_input_name) - all_input_names.extend(min_names) - all_input_names.extend(max_names) - quantized_concat_node = create_node("QuantizedConcatV2", - quantized_concat_name, all_input_names) - set_attr_int(quantized_concat_node, "N", len(original_inputs)) - set_attr_dtype(quantized_concat_node, "T", dtypes.quint8) - self.add_output_graph_node(quantized_concat_node) - self.add_dequantize_result_node(quantized_concat_name, original_node.name) - - def eightbitize_placeholder_node(self, current_node): - """Replaces a placeholder node with a quint8 placeholder node+dequantize.""" - name = current_node.name - - # Convert the placeholder into a quantized type. - output_node = node_def_pb2.NodeDef() - output_node.CopyFrom(current_node) - set_attr_dtype(output_node, "dtype", dtypes.quint8) - output_node.name += "_original_input" - self.add_output_graph_node(output_node) - - # Add a dequantize to convert back to float. - dequantize_node = create_node("Dequantize", name, [ - output_node.name, "quantized_input_min_value", - "quantized_input_max_value" - ]) - set_attr_dtype(dequantize_node, "T", dtypes.quint8) - set_attr_string(dequantize_node, "mode", b"MIN_FIRST") - self.add_output_graph_node(dequantize_node) - - # For the descent over the graph to work, the dequantize node must be named - # current_node.name. However, for the feeding of the graph to work, the - # placeholder must have the name current_node.name; so record a final set - # of renames to apply after all processing has been done. - self.final_node_renames[output_node.name] = name - self.final_node_renames[dequantize_node.name] = name + "_dequantize" - - def eightbitize_reshape_node(self, original_node): - """Replaces a Reshape node with the eight bit equivalent sub-graph. - - Args: - original_node: Float node to be converted. - - Returns: - Subgraph representing the quantized version of the original node. - - """ - namespace_prefix = original_node.name + "_eightbit" - quantized_reshape_name = namespace_prefix + "_quantized_reshape" - reshape_dims_name, reduction_dims_name = self.add_common_quantization_nodes( - namespace_prefix) - shape_input_name = original_node.input[1] - quantize_input_name, min_input_name, max_input_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_node.input[0], - reshape_dims_name, reduction_dims_name)) - quantized_reshape_node = create_node( - "QuantizedReshape", quantized_reshape_name, - [quantize_input_name, shape_input_name, min_input_name, max_input_name]) - set_attr_dtype(quantized_reshape_node, "T", dtypes.quint8) - self.add_output_graph_node(quantized_reshape_node) - self.add_dequantize_result_node(quantized_reshape_name, original_node.name) - - def eightbitize_batch_norm_node(self, original_node): - """Replaces a MatMul node with the eight bit equivalent sub-graph.""" - namespace_prefix = original_node.name + "_eightbit" - original_input_name = original_node.input[0] - original_mean_name = original_node.input[1] - original_variance_name = original_node.input[2] - original_beta_name = original_node.input[3] - original_gamma_name = original_node.input[4] - quantized_batch_norm_name = namespace_prefix + "_quantized_batch_norm" - - reshape_dims_name, reduction_dims_name = self.add_common_quantization_nodes( - namespace_prefix) - quantize_input_name, min_input_name, max_input_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_input_name, - reshape_dims_name, reduction_dims_name)) - quantize_mean_name, min_mean_name, max_mean_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_mean_name, - reshape_dims_name, reduction_dims_name)) - quantize_variance_name, min_variance_name, max_variance_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_variance_name, - reshape_dims_name, reduction_dims_name)) - quantize_beta_name, min_beta_name, max_beta_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_beta_name, - reshape_dims_name, reduction_dims_name)) - quantize_gamma_name, min_gamma_name, max_gamma_name = ( - self.eightbitize_input_to_node(namespace_prefix, original_gamma_name, - reshape_dims_name, reduction_dims_name)) - quantized_batch_norm_node = create_node( - "QuantizedBatchNormWithGlobalNormalization", quantized_batch_norm_name, - [ - quantize_input_name, min_input_name, max_input_name, - quantize_mean_name, min_mean_name, max_mean_name, - quantize_variance_name, min_variance_name, max_variance_name, - quantize_beta_name, min_beta_name, max_beta_name, - quantize_gamma_name, min_gamma_name, max_gamma_name - ]) - set_attr_dtype(quantized_batch_norm_node, "Tinput", dtypes.quint8) - set_attr_dtype(quantized_batch_norm_node, "out_type", dtypes.qint32) - copy_attr(quantized_batch_norm_node, "scale_after_normalization", - original_node.attr["scale_after_normalization"]) - copy_attr(quantized_batch_norm_node, "variance_epsilon", - original_node.attr["variance_epsilon"]) - self.add_output_graph_node(quantized_batch_norm_node) - quantize_down_name = self.add_quantize_down_nodes(original_node, - quantized_batch_norm_name) - self.add_dequantize_result_node(quantize_down_name, original_node.name) - - def add_output_graph_node(self, output_node): - """Inserts one node into the new graph.""" - self.output_graph.node.extend([output_node]) - - def remove_redundant_quantization(self, old_graph): - """Removes unneeded pairs of quantize/dequantize ops from the graph. - - This is a bit of a tricky function, because it's attempting to spot the - pattern of dequantizing from eight-bit up to float, and then immediately - quantizing back down to eight bits again, that's introduced by previous - passes that do 'key-hole' conversions of individual nodes but have to - convert back to float to match the previous output interface, since they - don't know that the next op can handle quantized tensors. - It works by: - - Looking for Quantize nodes. - - Checking to see if their first input is a Dequantize node. - - Seeing if their min/max inputs come from Min/Max nodes. - - Making sure those Min/Max nodes are being fed from the same Dequantize. - - Or that the Min is indirectly being fed from the same Dequantize as Max. - - Making sure the Dequantize is going through a Reshape (which we add - during the previous pass when we create the quantize sub-graph). - - Looking for the dims Const op for the Min/Max dims. - If all of these conditions are met, then it's a sub-graph pattern that - we know how to optimize out (and is likely the common one we've introduced). - We then rewire the graph to skip it entirely, and then rely on the dead node - removal pass to get rid of any nodes that are no longer needed. - - Args: - old_graph: The model we'll be stripping redundant nodes from. - - Returns: - A graph with the unnecessary nodes removed. - - Raises: - ValueError: Two nodes with the same name were found in the graph. - """ - old_nodes_map = self.create_nodes_map(old_graph) - self.output_graph = graph_pb2.GraphDef() - inputs_to_rename = {} - # We go through all the nodes, looking for any that match the patterns we - # know how to optimize away. - for node in old_graph.node: - # We always start with a Quantize node, and examine its inputs to see if - # they are in a form that can be removed. - if node.op not in ["Quantize", "QuantizeV2"]: - continue - dequantize_node_name = node_name_from_input(node.input[0]) - if dequantize_node_name not in old_nodes_map: - raise ValueError("Input node name '" + dequantize_node_name + - "' not found in node '" + node.name + "'") - dequantize_node = old_nodes_map[dequantize_node_name] - # Do we have a Dequantize feeding in, with the same type as the Quantize? - if dequantize_node.op != "Dequantize": - continue - if node.attr["T"] != dequantize_node.attr["T"]: - continue - # Now look at the other inputs, and ensure they're Min/Max nodes. - min_node_name = node_name_from_input(node.input[1]) - max_node_name = node_name_from_input(node.input[2]) - min_node = old_nodes_map[min_node_name] - max_node = old_nodes_map[max_node_name] - is_min_right_type = (min_node.op in ["Min", "Dequantize"]) - is_max_right_type = (max_node.op in ["Max", "Dequantize"]) - if not is_min_right_type or not is_max_right_type: - print("Didn't find expected types on inputs : %s, %s." % (min_node.op, - max_node.op)) - continue - min_node_input_name = node_name_from_input(min_node.input[0]) - max_node_input_name = node_name_from_input(max_node.input[0]) - # There are two different patterns for Min nodes we can recognize, one - # where the input comes directly from the same one as the Max, and - # another where we run it through another Min first, so check for both. - is_same_input = False - if min_node_input_name == max_node_input_name: - is_same_input = True - else: - first_min_node_input = old_nodes_map[min_node_input_name] - if first_min_node_input.op == "Concat": - second_min_node_name = node_name_from_input( - first_min_node_input.input[1]) - second_min_node = old_nodes_map[second_min_node_name] - if second_min_node.op == "Min": - second_min_node_input_name = node_name_from_input( - second_min_node.input[0]) - is_same_input = (second_min_node_input_name == max_node_input_name) - if not is_same_input: - print("Different min/max inputs: " + min_node_input_name) - continue - # We recognize this pattern, so mark the graph edges to be rewired to - # route around it entirely, since we know it's a no-op. - dequantize_source_name = node_name_from_input(dequantize_node.input[0]) - node_tensor_name = ensure_tensor_name_has_port(node.name) - min_tensor_name = node.name + ":1" - max_tensor_name = node.name + ":2" - inputs_to_rename[node_tensor_name] = dequantize_source_name - inputs_to_rename[min_tensor_name] = dequantize_node.input[1] - inputs_to_rename[max_tensor_name] = dequantize_node.input[2] - # Finally we apply all the rewiring we've marked to the graph. - for node in old_graph.node: - for index, input_full_name in enumerate(node.input): - input_name = ensure_tensor_name_has_port(input_full_name) - if input_name in inputs_to_rename: - node.input[index] = inputs_to_rename[input_name] - self.add_output_graph_node(node) - return self.output_graph - - def apply_final_node_renames(self): - """Applies node renames in self.final_node_renames to self.output_graph.""" - old_graph = self.output_graph - self.output_graph = graph_pb2.GraphDef() - for node in old_graph.node: - node.name = self.final_node_renames.get(node.name, node.name) - for index, input_name in enumerate(node.input): - node_name = node_name_from_input(input_name) - input_full_name = ensure_tensor_name_has_port(input_name) - if node_name in self.final_node_renames: - node.input[index] = "%s%s" % (self.final_node_renames[node_name], - input_full_name[len(node_name):]) - self.add_output_graph_node(node) - return self.output_graph - - def remove_dead_nodes(self, output_names): - """Removes nodes that are no longer needed for inference from the graph.""" - old_output_graph = self.output_graph - self.output_graph = graph_util.extract_sub_graph(old_output_graph, - output_names) - - def quantize_weights(self, input_graph, quantization_mode): - """Quantize float Const ops. - - There are two modes of operations, both replace float Const ops with - quantized values. - 1. If quantization_mode is "weights_rounded", this function replaces float - Const ops with quantized float Const ops - same as the original op, but - float values being mapped to the center of one of 1< Date: Wed, 3 Oct 2018 23:03:18 -0500 Subject: [PATCH 0045/1825] Static cast size_t to int in arguments 1,2 to forward_input_or_allocate_output() This fix resolves the following compiler error: tensorflow/core/kernels/mkl_relu_op.cc(1028): error C2398: Element '1': conversion from 'const std::size_t' to 'int' requires a narrowing conversion --- tensorflow/core/kernels/mkl_relu_op.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/mkl_relu_op.cc b/tensorflow/core/kernels/mkl_relu_op.cc index 84385356e1..3d145f1802 100644 --- a/tensorflow/core/kernels/mkl_relu_op.cc +++ b/tensorflow/core/kernels/mkl_relu_op.cc @@ -1023,7 +1023,8 @@ class MklReluGradOpBase : public OpKernel { } OP_REQUIRES_OK(context, context->forward_input_or_allocate_output( - {diff_dst_index}, diff_src_index, + {static_cast(diff_dst_index)}, + static_cast(diff_src_index), tf_shape_diff_src, &diff_src_tensor)); AllocateOutputSetMklShape(context, diff_src_index, dnn_shape_diff_src); -- GitLab From ba95d09288d695ea8875ca294b2519110f2d56d0 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 4 Oct 2018 17:06:23 +0200 Subject: [PATCH 0046/1825] MPI libraries maybe located under lib64 or lib32 --- configure.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/configure.py b/configure.py index a88fdb3555..f72820ab02 100644 --- a/configure.py +++ b/configure.py @@ -1417,12 +1417,16 @@ def set_mpi_home(environ_cp): def valid_mpi_path(mpi_home): exists = ( - os.path.exists(os.path.join(mpi_home, 'include')) and - os.path.exists(os.path.join(mpi_home, 'lib'))) + os.path.exists(os.path.join(mpi_home, 'include')) and ( + os.path.exists(os.path.join(mpi_home, 'lib')) or + os.path.exists(os.path.join(mpi_home, 'lib64')) or + os.path.exists(os.path.join(mpi_home, 'lib32')))) if not exists: - print('Invalid path to the MPI Toolkit. %s or %s cannot be found' % + print('Invalid path to the MPI Toolkit. %s or %s or %s or %s cannot be found' % (os.path.join(mpi_home, 'include'), - os.path.exists(os.path.join(mpi_home, 'lib')))) + os.path.exists(os.path.join(mpi_home, 'lib')), + os.path.exists(os.path.join(mpi_home, 'lib64')), + os.path.exists(os.path.join(mpi_home, 'lib32')))) return exists _ = prompt_loop_or_load_from_env( @@ -1463,8 +1467,15 @@ def set_other_mpi_vars(environ_cp): if os.path.exists(os.path.join(mpi_home, 'lib/libmpi.so')): symlink_force( os.path.join(mpi_home, 'lib/libmpi.so'), 'third_party/mpi/libmpi.so') + elif os.path.exists(os.path.join(mpi_home, 'lib64/libmpi.so')): + symlink_force( + os.path.join(mpi_home, 'lib64/libmpi.so'), 'third_party/mpi/libmpi.so') + elif os.path.exists(os.path.join(mpi_home, 'lib32/libmpi.so')): + symlink_force( + os.path.join(mpi_home, 'lib32/libmpi.so'), 'third_party/mpi/libmpi.so') + else: - raise ValueError('Cannot find the MPI library file in %s/lib' % mpi_home) + raise ValueError('Cannot find the MPI library file in %s/lib or %s/lib64 or %s/lib32' % mpi_home, mpi_home, mpi_home) def set_system_libs_flag(environ_cp): -- GitLab From 10f5bbd27382c17defd2029c791a42a4e9e431fd Mon Sep 17 00:00:00 2001 From: mdfaijul Date: Thu, 4 Oct 2018 13:08:15 -0700 Subject: [PATCH 0047/1825] ran clang+llvm-3.9.0. --- tensorflow/core/kernels/mkl_conv_ops.cc | 100 ++++++++++++------------ 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc index dfad990aac..1ecc15d459 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_ops.cc @@ -323,7 +323,7 @@ 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 new primitive */ conv_fwd = new MklConvFwdPrimitive( convFwdDims); } else { @@ -425,16 +425,15 @@ class MklConvOp : public OpKernel { OP_REQUIRES(context, FormatFromString(data_format, &data_format_), errors::InvalidArgument("Invalid data format")); OP_REQUIRES(context, strides_.size() == 4, - errors::InvalidArgument( - "Sliding window strides field must " - "specify 4 dimensions")); + errors::InvalidArgument("Sliding window strides field must " + "specify 4 dimensions")); const int64 stride_n = GetTensorDim(strides_, data_format_, 'N'); const int64 stride_c = GetTensorDim(strides_, data_format_, 'C'); - 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( + 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("padding", &padding_)); } @@ -729,7 +728,7 @@ class MklConvOp : public OpKernel { mkl_prim_convert_input; dnnLayout_t mkl_lt_internal_filter, mkl_lt_internal_bias, mkl_lt_internal_input; - void* mkl_buf_convert_input, *mkl_buf_convert_filter, + void *mkl_buf_convert_input, *mkl_buf_convert_filter, *mkl_buf_convert_bias; mkl_prim_convert_filter = nullptr; mkl_prim_convert_bias = nullptr; @@ -862,23 +861,21 @@ class MklConvOp : public OpKernel { OP_REQUIRES(context, FormatFromString(data_format, &data_format_), errors::InvalidArgument("Invalid data format")); OP_REQUIRES(context, (strides_.size() == 4 || strides_.size() == 5), - errors::InvalidArgument( - "Sliding window strides field must " - "specify 4 or 5 dimensions")); + errors::InvalidArgument("Sliding window strides field must " + "specify 4 or 5 dimensions")); const int64 stride_n = GetTensorDim(strides_, data_format_, 'N'); const int64 stride_c = GetTensorDim(strides_, data_format_, 'C'); - 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( + 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("padding", &padding_)); if (strides_.size() == 4) { OP_REQUIRES(context, dilations_.size() == 4, - errors::InvalidArgument( - "Sliding window dilations field must " - "specify 4 dimensions")); + errors::InvalidArgument("Sliding window dilations field must " + "specify 4 dimensions")); const int64 dilation_n = GetTensorDim(dilations_, data_format_, 'N'); const int64 dilation_c = GetTensorDim(dilations_, data_format_, 'C'); const int64 dilation_h = GetTensorDim(dilations_, data_format_, 'H'); @@ -892,9 +889,8 @@ class MklConvOp : public OpKernel { errors::InvalidArgument("Dilated rates should be larger than 0.")); } else if (strides_.size() == 5) { OP_REQUIRES(context, dilations_.size() == 5, - errors::InvalidArgument( - "Dilation rates field must " - "specify 5 dimensions")); + errors::InvalidArgument("Dilation rates field must " + "specify 5 dimensions")); OP_REQUIRES(context, (GetTensorDim(dilations_, data_format_, 'N') == 1 && GetTensorDim(dilations_, data_format_, 'C') == 1), errors::InvalidArgument( @@ -918,9 +914,8 @@ class MklConvOp : public OpKernel { GetMklShape(context, kInputIndex_Src, &src_mkl_shape); GetMklShape(context, kInputIndex_Filter, &filter_mkl_shape); OP_REQUIRES(context, filter_mkl_shape.IsMklTensor() == false, - errors::InvalidArgument( - "Filter should not be in " - "Mkl Layout")); + errors::InvalidArgument("Filter should not be in " + "Mkl Layout")); MklDnnData src(&cpu_engine_); MklDnnData filter(&cpu_engine_); @@ -956,8 +951,9 @@ class MklConvOp : public OpKernel { 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)) { + if (typeid(Tinput) == typeid(float) && + typeid(Tfilter) == typeid(float) && + typeid(Toutput) == typeid(float)) { filter_mkl_shape.SetMklTensor(false); AllocateOutputSetMklShape(context, kOutputIndex_Filter, &output_filter_tensor, filter_tf_shape, @@ -1044,8 +1040,8 @@ class MklConvOp : public OpKernel { AllocateOutputTensor(context, *conv_fwd_pd, dst_dims_mkl_order, tf_fmt, &dst_tensor); Tensor* filter_out_tensor = nullptr; - if (typeid(Tinput) == typeid(float)&&typeid(Tfilter) == - typeid(float)&&typeid(Toutput) == typeid(float)) { + if (typeid(Tinput) == typeid(float) && typeid(Tfilter) == typeid(float) && + typeid(Toutput) == typeid(float)) { AllocateFilterOutputTensor(context, *conv_fwd_pd, TFShapeToMklDnnDims(filter_tf_shape), &filter_out_tensor); @@ -1088,8 +1084,7 @@ class MklConvOp : public OpKernel { // delete primitive since it is not cached. if (do_not_cache) delete conv_fwd; - } - catch (mkldnn::error& e) { + } catch (mkldnn::error& e) { string error_msg = tensorflow::strings::StrCat( "Status: ", e.status, ", message: ", string(e.message), ", in file ", __FILE__, ":", __LINE__); @@ -1785,31 +1780,34 @@ REGISTER_KERNEL_BUILDER( #endif // INTEL_MKL_ML // Register 2D operations -#define REGISTER_MKL_CPU_2D(T) \ - REGISTER_KERNEL_BUILDER( \ - Name("_MklConv2D").Device(DEVICE_CPU).TypeConstraint("T").Label( \ - mkl_op_registry::kMklOpLabel), \ - MklConvOp); \ - REGISTER_KERNEL_BUILDER( \ - Name("_MklConv2DWithBias") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConvOp); \ - REGISTER_KERNEL_BUILDER(Name("__MklDummyConv2DWithBias") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ +#define REGISTER_MKL_CPU_2D(T) \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklConv2D") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvOp); \ + REGISTER_KERNEL_BUILDER( \ + Name("_MklConv2DWithBias") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvOp); \ + REGISTER_KERNEL_BUILDER(Name("__MklDummyConv2DWithBias") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ MklDummyOp); TF_CALL_float(REGISTER_MKL_CPU_2D); // Register 3D operations -#define REGISTER_MKL_CPU_3D(T) \ - REGISTER_KERNEL_BUILDER( \ - Name("_MklConv3D").Device(DEVICE_CPU).TypeConstraint("T").Label( \ - mkl_op_registry::kMklOpLabel), \ - MklConvOp); +#define REGISTER_MKL_CPU_3D(T) \ + REGISTER_KERNEL_BUILDER(Name("_MklConv3D") \ + .Device(DEVICE_CPU) \ + .TypeConstraint("T") \ + .Label(mkl_op_registry::kMklOpLabel), \ + MklConvOp); TF_CALL_float(REGISTER_MKL_CPU_3D); } // namespace tensorflow -- GitLab From bf94614e9540e23d808bdc15ce1af1f53f662d13 Mon Sep 17 00:00:00 2001 From: Michael Gielda Date: Fri, 5 Oct 2018 00:09:37 +0200 Subject: [PATCH 0048/1825] Take ALL TESTS PASSED in ticks for good formatting --- tensorflow/contrib/lite/experimental/micro/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/lite/experimental/micro/README.md b/tensorflow/contrib/lite/experimental/micro/README.md index 414cafde4d..9add470761 100644 --- a/tensorflow/contrib/lite/experimental/micro/README.md +++ b/tensorflow/contrib/lite/experimental/micro/README.md @@ -64,7 +64,7 @@ TF_LITE_MICRO_TEST(SimpleTest) { TF_LITE_MICRO_TESTS_END ``` -These macros work a lot like [the Google test framework](https://github.com/google/googletest), but they don't require any dependencies and just write results to stderr, rather than aborting the program. If all the tests pass, then "~~~ALL TESTS PASSED~~~" is 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. +These macros work a lot like [the Google test framework](https://github.com/google/googletest), but they don't require any dependencies and just write results to stderr, rather than aborting the program. If all the tests pass, then `~~~ALL TESTS PASSED~~~` is 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. -- GitLab From f1ed49830ee66afdad0ae13fa22722754b278ce2 Mon Sep 17 00:00:00 2001 From: Michael Gielda Date: Fri, 5 Oct 2018 00:11:10 +0200 Subject: [PATCH 0049/1825] Use ticks in all three occurrences of ALL TESTS... --- tensorflow/contrib/lite/experimental/micro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/lite/experimental/micro/README.md b/tensorflow/contrib/lite/experimental/micro/README.md index 9add470761..6b7712c25d 100644 --- a/tensorflow/contrib/lite/experimental/micro/README.md +++ b/tensorflow/contrib/lite/experimental/micro/README.md @@ -36,7 +36,7 @@ Building requires a Linux or OS X machine. - Download the dependencies by running `tensorflow/contrib/lite/experimental/micro/tools/make/download_dependencies.sh`. This may take a few minutes - Build and test the library with `make -f tensorflow/contrib/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. +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: @@ -111,4 +111,4 @@ LOGS: tensorflow/contrib/lite/experimental/micro/tools/make/gen/bluepill_cortex-m3/bin/tensorflow/contrib/lite/experimental/micro/kernels/depthwise_conv_test: PASS ``` -There's a lot of output here, but you should be able to see that the same tests that were covered when we ran locally on the development machine show up in the debug logs here, along with the magic string "~~~ALL TESTS PASSED~~~". This is the exact same code as before, just compiled and run on the STM32F103 rather than your desktop. We hope that the simplicity of this testing approach will help make adding support for new platforms as easy as possible. +There's a lot of output here, but you should be able to see that the same tests that were covered when we ran locally on the development machine show up in the debug logs here, along with the magic string `~~~ALL TESTS PASSED~~~`. This is the exact same code as before, just compiled and run on the STM32F103 rather than your desktop. We hope that the simplicity of this testing approach will help make adding support for new platforms as easy as possible. -- GitLab From 4e6045b06ca1d80e7c86a92ebbe839d849d5ae4a Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 19 Sep 2018 13:25:49 -0700 Subject: [PATCH 0050/1825] Add MatchingFilesDatasetOp --- tensorflow/core/kernels/data/BUILD | 15 + .../kernels/data/matching_files_dataset_op.cc | 330 ++++++++++++++++++ tensorflow/core/ops/dataset_ops.cc | 12 + tensorflow/python/data/kernel_tests/BUILD | 22 ++ .../matching_files_dataset_op_test.py | 240 +++++++++++++ tensorflow/python/data/ops/dataset_ops.py | 37 +- 6 files changed, 650 insertions(+), 6 deletions(-) create mode 100644 tensorflow/core/kernels/data/matching_files_dataset_op.cc create mode 100644 tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index 37c1c54786..23ddf32be7 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -750,6 +750,7 @@ tf_kernel_library( ":map_and_batch_dataset_op", ":map_dataset_op", ":map_defun_op", + ":matching_files_dataset_op", ":model_dataset_op", ":multi_device_iterator_ops", ":optimize_dataset_op", @@ -808,3 +809,17 @@ tf_kernel_library( "//tensorflow/core:lib_internal", ], ) + + +tf_kernel_library( + name = "matching_files_dataset_op", + srcs = ["matching_files_dataset_op.cc"], + deps = [ + ":dataset", + "//tensorflow/core:dataset_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + ], +) + diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc new file mode 100644 index 0000000000..b43517d0f5 --- /dev/null +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -0,0 +1,330 @@ +/* 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 "tensorflow/core/framework/partial_tensor_shape.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/kernels/data/dataset.h" +#include "tensorflow/core/lib/io/buffered_inputstream.h" +#include "tensorflow/core/lib/io/inputbuffer.h" +#include "tensorflow/core/lib/io/random_inputstream.h" +#include "tensorflow/core/lib/io/record_reader.h" +#include "tensorflow/core/lib/io/zlib_compression_options.h" +#include "tensorflow/core/lib/io/zlib_inputstream.h" +#include "tensorflow/core/framework/op_kernel.h" +#include "tensorflow/core/framework/tensor_shape.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/platform/env.h" +#include "tensorflow/core/lib/io/path.h" +#include "tensorflow/core/lib/core/threadpool.h" + +namespace tensorflow { +namespace data { + +namespace { + +constexpr int kNumThreads = 8; + +// Run a function in parallel using a ThreadPool, but skip the ThreadPool +// on the iOS platform due to its problems with more than a few threads. +void ForEach(int first, int last, const std::function& f) { +#if TARGET_OS_IPHONE + for (int i = first; i < last; i++) { + f(i); + } +#else + int num_threads = std::min(kNumThreads, last - first); + thread::ThreadPool threads(Env::Default(), "ForEach", num_threads); + for (int i = first; i < last; i++) { + threads.Schedule([f, i] { f(i); }); + } +#endif +} + +} // namespace + +namespace { + +class MatchingFilesDatasetOp : public DatasetOpKernel { + public: + using DatasetOpKernel::DatasetOpKernel; + + void MakeDataset(OpKernelContext* ctx, DatasetBase** output) override { + const Tensor* patterns_t; + // NOTE(originally from ringwalt): Changing the input name "pattern" to + // "patterns" would break existing graphs. + OP_REQUIRES_OK(ctx, ctx->input("pattern", &patterns_t)); + OP_REQUIRES( + ctx, + TensorShapeUtils::IsScalar(patterns_t->shape()) || + TensorShapeUtils::IsVector(patterns_t->shape()), + errors::InvalidArgument( + "Input patterns tensor must be scalar or vector, but had shape: ", + patterns_t->shape().DebugString())); + const auto patterns = patterns_t->flat(); + size_t num_patterns = static_cast(patterns.size()); + std::vector pattern_strs; + pattern_strs.reserve(num_patterns); + + for (int i = 0; i < num_patterns; ++i) { + pattern_strs.push_back(patterns(i)); + } + + // keep the elements in the descending order + std::sort(pattern_strs.begin(), pattern_strs.end(), std::greater()); + *output = new Dataset(ctx, std::move(pattern_strs)); + } + + private: + class Dataset : public DatasetBase { + public: + Dataset(OpKernelContext* ctx, std::vector patterns) + : DatasetBase(DatasetContext(ctx)), + pattern_(std::move(patterns)) {} + + std::unique_ptr MakeIteratorInternal( + const string& prefix) const override { + return std::unique_ptr( + new Iterator({this, strings::StrCat(prefix, "::FileName")})); + } + + const DataTypeVector& output_dtypes() const override { + static DataTypeVector* dtypes = new DataTypeVector({DT_STRING}); + return *dtypes; + } + + const std::vector& output_shapes() const override { + static std::vector* shapes = + new std::vector({{}}); + return *shapes; + } + + string DebugString() const override { + return "MatchingFilesDatasetOp::Dataset"; + } + + protected: + Status AsGraphDefInternal(SerializationContext* ctx, + DatasetGraphDefBuilder* b, + Node** output) const override { + Node* pattern = nullptr; + TF_RETURN_IF_ERROR(b->AddVector(pattern_, &pattern)); + TF_RETURN_IF_ERROR(b->AddDataset(this, {pattern}, output)); + return Status::OK(); + } + + private: + class Iterator : public DatasetIterator { + public: + explicit Iterator(const Params& params) + : DatasetIterator(params) {} + + Status GetNextInternal(IteratorContext* ctx, + std::vector* out_tensors, + bool* end_of_sequence) override { + mutex_lock l(mu_); + Status ret; + + while (!filepath_queue_.empty() || + current_pattern_index_ < dataset()->pattern_.size()) { + // all the elements in the heap will be the matched file name or the + // potential directory + if (!filepath_queue_.empty()) { + string cur_file = filepath_queue_.top(); + filepath_queue_.pop(); + + // we can also use isDectory() here. But IsDirectory call can be + // expensive for some FS + if (ctx->env()->MatchPath(cur_file, current_pattern_)){ + Tensor filepath_tensor(ctx->allocator({}), DT_STRING, {}); + filepath_tensor.scalar()() = cur_file; + out_tensors->emplace_back(std::move(filepath_tensor)); + *end_of_sequence = false; + return Status::OK(); + } + + // in this case, cur_file is a directory. Then create a sub-pattern + // to continue the search + size_t pos = current_pattern_.find_first_of("*?[\\"); + size_t len = current_pattern_.size() - pos; + string cur_pattern_suffix = current_pattern_.substr(pos, len); + string sub_pattern = strings::StrCat(cur_file, + "/", + cur_pattern_suffix); + Status s = UpdateIterator(ctx->env(), sub_pattern); + ret.Update(s); + } else { + // search a new pattern + current_pattern_ = dataset()->pattern_[current_pattern_index_]; + Status s = UpdateIterator(ctx->env(), current_pattern_); + ret.Update(s); + ++current_pattern_index_; + } + } + + *end_of_sequence = true; + return Status::OK(); + } + + protected: + Status SaveInternal(IteratorStateWriter* writer) override { + mutex_lock l(mu_); + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name("current_pattern_index"), + current_pattern_index_)); + + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name("current_pattern"), + current_pattern_)); + + if (!filepath_queue_.empty()) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name("queue_size"), filepath_queue_.size())); + for (int i = 0; i < filepath_queue_.size(); ++i) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat("queue_element_", i)), + filepath_queue_.top())); + filepath_queue_.pop(); + } + } + } + + Status RestoreInternal(IteratorContext* ctx, + IteratorStateReader* reader) override { + mutex_lock l(mu_); + int64 current_pattern_index; + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("current_pattern_index"), + ¤t_pattern_index)); + current_pattern_index_ = size_t(current_pattern_index); + + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("current_pattern"), + ¤t_pattern_)); + + int64 queue_size; + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("queue_size"), + &queue_size)); + for (int i = static_cast(queue_size - 1); i >= 0; --i) { + string element; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat("queue_element_", i)), &element)); + filepath_queue_.push(element); + } + return Status::OK(); + } + + private: + Status UpdateIterator(Env *env, const string &pattern) + EXCLUSIVE_LOCKS_REQUIRED(mu_) { + string fixed_prefix = pattern.substr(0, pattern.find_first_of("*?[\\")); + string eval_pattern = pattern; + string dir(io::Dirname(fixed_prefix)); + + // If dir is empty then we need to fix up fixed_prefix and eval_pattern to + // include . as the top level directory. + if (dir.empty()) { + dir = "."; + fixed_prefix = io::JoinPath(dir, fixed_prefix); + eval_pattern = io::JoinPath(dir, pattern); + } + + FileSystem* fs; + TF_RETURN_IF_ERROR(env->GetFileSystemForFile(dir, &fs)); + + filepath_queue_.push(dir); + Status ret; //Status to return + // children_dir_status holds is_dir status for children. It can have three + // possible values: OK for true; FAILED_PRECONDITION for false; CANCELLED + // if we don't calculate IsDirectory (we might do that because there isn't + // any point in exploring that child path). + + // DFS to find the first element in the iterator + while (!filepath_queue_.empty()) { + string cur_dir = filepath_queue_.top(); + filepath_queue_.pop(); + std::vector children; + Status s = fs->GetChildren(cur_dir, &children); + ret.Update(s); + + // if cur_dir has no children, there will two possible situations: 1) + // the cur_dir is an empty dir; 2) the cur_dir is actual a file + // instead of a director. For the first one, continue to search the + // heap; For the second one, if the file matches the pattern, add + // it to the heap and finish the search; otherwise, continue the next + // search + if (children.empty()) { + if (env->MatchPath(cur_dir, current_pattern_)) { + filepath_queue_.push(cur_dir); + return ret; + } else { + continue; + } + } + + std::map children_dir_status; + // This IsDirectory call can be expensive for some FS. Parallelizing it. + ForEach(0, children.size(), + [fs, &cur_dir, &children, &fixed_prefix, + &children_dir_status] (int i) { + const string child_path = io::JoinPath(cur_dir, children[i]); + // In case the child_path doesn't start with the fixed_prefix then + // we don't need to explore this path. + if (!str_util::StartsWith(child_path, fixed_prefix)) { + children_dir_status[child_path] = + Status(tensorflow::error::CANCELLED, + "Operation not needed"); + } else { + children_dir_status[child_path] = fs->IsDirectory(child_path); + } + }); + + for (const auto &child : children) { + const string child_dir_path = io::JoinPath(cur_dir, child); + const Status child_dir_status = children_dir_status[child]; + // If the IsDirectory call was cancelled we bail. + if (child_dir_status.code() == tensorflow::error::CANCELLED) { + continue; + } + + if (child_dir_status.ok()) { + //push the child dir for next search + filepath_queue_.push(child_dir_path); + } else { + // this case will be a file; if the file match the pattern, push + // it to the heap; otherwise, ignore it + if (env->MatchPath(child_dir_path, eval_pattern)) { + filepath_queue_.push(child_dir_path); + } + } + } + } + return ret; + } + + mutex mu_; + //std::unique_ptr> filepath_queue_ GUARDED_BY(mu_); + std::priority_queue filepath_queue_ GUARDED_BY(mu_); // = new std::priority_queue; + size_t current_pattern_index_ GUARDED_BY(mu_) = 0; + string current_pattern_ GUARDED_BY(mu_); + }; + + const std::vector pattern_; + }; +}; + +REGISTER_KERNEL_BUILDER(Name("MatchingFilesDataset").Device(DEVICE_CPU), + MatchingFilesDatasetOp); + +} // namespace +} // namespace data +} // namespace tensorflow \ No newline at end of file diff --git a/tensorflow/core/ops/dataset_ops.cc b/tensorflow/core/ops/dataset_ops.cc index ec22eee874..26c2756836 100644 --- a/tensorflow/core/ops/dataset_ops.cc +++ b/tensorflow/core/ops/dataset_ops.cc @@ -619,6 +619,18 @@ REGISTER_OP("TextLineDataset") return shape_inference::ScalarShape(c); }); +REGISTER_OP("MatchingFilesDataset") + .Input("pattern: string") + .Output("handle: variant") + .SetIsStateful() // TODO(b/65524810): Source dataset ops must be marked + // stateful to inhibit constant folding. + .SetShapeFn([](shape_inference::InferenceContext* c) { + shape_inference::ShapeHandle unused; + // `patterns` must be a scalar or a vector. + TF_RETURN_IF_ERROR(c->WithRankAtMost(c->input(0), 1, &unused)); + return shape_inference::ScalarShape(c); + }); + REGISTER_OP("SqlDataset") .Input("driver_name: string") .Input("data_source_name: string") diff --git a/tensorflow/python/data/kernel_tests/BUILD b/tensorflow/python/data/kernel_tests/BUILD index ecb24103b3..89b5fde727 100644 --- a/tensorflow/python/data/kernel_tests/BUILD +++ b/tensorflow/python/data/kernel_tests/BUILD @@ -331,6 +331,28 @@ cuda_py_test( ], ) +tf_py_test( + name = "matching_files_dataset_op_test", + size = "small", + srcs = ["matching_files_dataset_op_test.py"], + additional_deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:constant_op", + "//tensorflow/python:dataset_ops_gen", + "//tensorflow/python:dtypes", + "//tensorflow/python:errors", + "//tensorflow/python:framework_ops", + "//tensorflow/python:io_ops", + "//tensorflow/python:lib", + "//tensorflow/python:parsing_ops", + "//tensorflow/python:tensor_shape", + "//tensorflow/python:util", + "//tensorflow/python/data/ops:iterator_ops", + "//tensorflow/python/data/ops:readers", + ], +) + tf_py_test( name = "prefetch_dataset_op_test", size = "small", diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py new file mode 100644 index 0000000000..ec2f165364 --- /dev/null +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -0,0 +1,240 @@ +# 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 the experimental input pipeline ops.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from os import path +import shutil +import tempfile + +from tensorflow.python.data.ops import iterator_ops +from tensorflow.python.data.ops.dataset_ops import MatchingFilesDataset +from tensorflow.python.framework import dtypes +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import test +from tensorflow.python.util import compat +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.ops.gen_io_ops import matching_files +from tensorflow.python.framework import errors + +import os +import time +from functools import partial + + +try: + import psutil # pylint: disable=g-import-not-at-top + + psutil_import_succeeded = True +except ImportError: + psutil_import_succeeded = False + + +def timeit(fn, msg, N=0): + start = time.time() + res = fn() + end = time.time() + runtime = (end - start) * 1000 + msg = '{}: time: {:.2f} ms'.format(msg, runtime) + if N: + msg += ' ({:.2f} ms per iteration)'.format(runtime / N) + print(msg) + return res + + +width = 10 +depth = 2 + + +class MatchingFilesDatasetTest(test.TestCase): + + def setUp(self): + self.tmp_dir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.tmp_dir, ignore_errors=True) + + def _touchTempFiles(self, filenames): + for filename in filenames: + open(path.join(self.tmp_dir, filename), 'a').close() + + def testEmptyDirectory(self): + dataset = MatchingFilesDataset(path.join(self.tmp_dir, '*')) + with self.cached_session() as sess: + itr = iterator_ops.Iterator.from_structure(dataset.output_types) + init_op = itr.make_initializer(dataset) + next_element = itr.get_next() + sess.run(init_op) + with self.assertRaises(errors.OutOfRangeError): + sess.run(next_element) + + def testSimpleDirectory(self): + filenames = ['a', 'b', 'c'] + self._touchTempFiles(filenames) + + dataset = MatchingFilesDataset(path.join(self.tmp_dir, '*')) + with self.cached_session() as sess: + itr = iterator_ops.Iterator.from_structure(dataset.output_types) + init_op = itr.make_initializer(dataset) + next_element = itr.get_next() + sess.run(init_op) + + full_filenames = [] + produced_filenames = [] + for filename in filenames: + full_filenames.append( + compat.as_bytes(path.join(self.tmp_dir, filename))) + produced_filenames.append(compat.as_bytes(sess.run(next_element))) + self.assertItemsEqual(full_filenames, produced_filenames) + with self.assertRaises(errors.OutOfRangeError): + sess.run(itr.get_next()) + + def testSimpleDirectoryInitializer(self): + filenames = ['a', 'b', 'c'] + self._touchTempFiles(filenames) + + filename_placeholder = array_ops.placeholder(dtypes.string, shape=[]) + dataset = MatchingFilesDataset(filename_placeholder) + + with self.cached_session() as sess: + itr = iterator_ops.Iterator.from_structure(dataset.output_types) + init_op = itr.make_initializer(dataset) + next_element = itr.get_next() + sess.run( + init_op, + feed_dict={filename_placeholder: path.join(self.tmp_dir, '*')}) + + full_filenames = [] + produced_filenames = [] + for filename in filenames: + full_filenames.append( + compat.as_bytes(path.join(self.tmp_dir, filename))) + produced_filenames.append(compat.as_bytes(sess.run(next_element))) + + self.assertItemsEqual(full_filenames, produced_filenames) + + with self.assertRaises(errors.OutOfRangeError): + sess.run(itr.get_next()) + + def testFileSuffixes(self): + filenames = ['a.txt', 'b.py', 'c.py', 'd.pyc'] + self._touchTempFiles(filenames) + + filename_placeholder = array_ops.placeholder(dtypes.string, shape=[]) + dataset = MatchingFilesDataset(filename_placeholder) + + with self.cached_session() as sess: + itr = iterator_ops.Iterator.from_structure(dataset.output_types) + init_op = itr.make_initializer(dataset) + next_element = itr.get_next() + sess.run( + init_op, + feed_dict={filename_placeholder: path.join(self.tmp_dir, '*.py')}) + + full_filenames = [] + produced_filenames = [] + for filename in filenames[1:-1]: + full_filenames.append( + compat.as_bytes(path.join(self.tmp_dir, filename))) + produced_filenames.append(compat.as_bytes(sess.run(next_element))) + self.assertItemsEqual(full_filenames, produced_filenames) + + with self.assertRaises(errors.OutOfRangeError): + sess.run(itr.get_next()) + + def testFileMiddles(self): + filenames = ['a.txt', 'b.py', 'c.pyc'] + self._touchTempFiles(filenames) + + filename_placeholder = array_ops.placeholder(dtypes.string, shape=[]) + dataset = MatchingFilesDataset(filename_placeholder) + + with self.cached_session() as sess: + itr = iterator_ops.Iterator.from_structure(dataset.output_types) + init_op = itr.make_initializer(dataset) + next_element = itr.get_next() + sess.run( + init_op, + feed_dict={filename_placeholder: path.join(self.tmp_dir, '*.py*')}) + + full_filenames = [] + produced_filenames = [] + for filename in filenames[1:]: + full_filenames.append( + compat.as_bytes(path.join(self.tmp_dir, filename))) + produced_filenames.append(compat.as_bytes(sess.run(next_element))) + + self.assertItemsEqual(full_filenames, produced_filenames) + + with self.assertRaises(errors.OutOfRangeError): + sess.run(itr.get_next()) + + def _load_data(self): + new_files = [] + dir = "/tmp/test/" + if not os.path.exists(dir): + os.makedirs(dir) + base = tempfile.mkdtemp(prefix=dir) + print('saving files to dir: {}'.format(base)) + for i in range(width): + new_base = os.path.join(base, str(i), *[str(j) for j in range(depth - 1)]) + if not os.path.exists(new_base): + os.makedirs(new_base) + f = os.path.join(new_base, 'stuff.txt') + new_files.append(compat.as_bytes(f)) + open(f, 'w').close() + return base, new_files + + def _read_data(self, data, sess, N=1): + for _ in range(N): + sess.run(data) + + def _read_data_with_result(self, data, sess, N=1): + result = [] + for _ in range(N): + result.append(sess.run(data)) + return result + + def testPerformance(self): + base, test_filenames = self._load_data() + test_filenames.sort(reverse=True) + patterns = array_ops.placeholder(dtypes.string, shape=[None]) + dataset = MatchingFilesDataset(patterns) + iterator = iterator_ops.Iterator.from_structure(dataset.output_types) + init_op = iterator.make_initializer(dataset) + get_next = iterator.get_next() + result = [] + with self.cached_session() as sess: + search_patterns = [base + "/*/*/*.txt"] + sess.run(init_op, feed_dict={patterns: search_patterns}) + result.extend(timeit(partial(self._read_data_with_result, get_next, sess), + "read first filename")) + result.extend(timeit(partial(self._read_data_with_result, get_next, sess), + "read second filename")) + N = width * len(search_patterns) - 2 + filename = timeit(partial(self._read_data_with_result, get_next, sess, N), + 'read {} more filenames'.format(N), N) + result.extend(filename) + + matched_filenames = [compat.as_bytes(x) for x in result] + for file in matched_filenames: + print(file) + self.assertItemsEqual(matched_filenames, test_filenames) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 2d036fd0d6..5150c7fb9a 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2624,7 +2624,7 @@ class MapDataset(UnaryDataset): self._use_inter_op_parallelism = use_inter_op_parallelism wrapped_func = StructuredFunctionWrapper( - map_func, "Dataset.map()", input_dataset) + map_func, "Dataset.map()", input_dataset) self._output_classes = wrapped_func.output_classes self._output_shapes = wrapped_func.output_shapes self._output_types = wrapped_func.output_types @@ -2633,11 +2633,11 @@ class MapDataset(UnaryDataset): def _as_variant_tensor(self): input_t = self._input_dataset._as_variant_tensor() # pylint: disable=protected-access return gen_dataset_ops.map_dataset( - input_t, - self._map_func.captured_inputs, - f=self._map_func, - use_inter_op_parallelism=self._use_inter_op_parallelism, - **flat_structure(self)) + input_t, + self._map_func.captured_inputs, + f=self._map_func, + use_inter_op_parallelism=self._use_inter_op_parallelism, + **flat_structure(self)) @property def output_classes(self): @@ -2652,6 +2652,31 @@ class MapDataset(UnaryDataset): return self._output_types +class MatchingFilesDataset(Dataset): + """A `Dataset` that list the files according to the input patterns""" + + def __init__(self, pattern): + super(MatchingFilesDataset, self).__init__() + self._pattern = ops.convert_to_tensor( + pattern, dtype=dtypes.string, name="pattern") + + + def _as_variant_tensor(self): + return gen_dataset_ops.matching_files_dataset(self._pattern) + + @property + def output_classes(self): + return ops.Tensor + + @property + def output_shapes(self): + return tensor_shape.scalar() + + @property + def output_types(self): + return dtypes.string + + class ParallelMapDataset(MapDataset): """A `Dataset` that maps a function over elements in its input in parallel.""" -- GitLab From a74b6598e919e06221bd793c2031182250cdcdff Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 19 Sep 2018 14:15:01 -0700 Subject: [PATCH 0051/1825] Update the performance test case --- .../kernel_tests/matching_files_dataset_op_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index ec2f165364..73e5d3e4d1 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -56,8 +56,8 @@ def timeit(fn, msg, N=0): return res -width = 10 -depth = 2 +width = 1000 +depth = 20 class MatchingFilesDatasetTest(test.TestCase): @@ -219,7 +219,9 @@ class MatchingFilesDatasetTest(test.TestCase): get_next = iterator.get_next() result = [] with self.cached_session() as sess: - search_patterns = [base + "/*/*/*.txt"] + pattern = '{}/{}/*.txt'\ + .format(base, os.path.join(*['**' for _ in range(depth)])) + search_patterns = [pattern] sess.run(init_op, feed_dict={patterns: search_patterns}) result.extend(timeit(partial(self._read_data_with_result, get_next, sess), "read first filename")) @@ -231,8 +233,6 @@ class MatchingFilesDatasetTest(test.TestCase): result.extend(filename) matched_filenames = [compat.as_bytes(x) for x in result] - for file in matched_filenames: - print(file) self.assertItemsEqual(matched_filenames, test_filenames) -- GitLab From 4259ac37aa126ce1bdd9c92e9b3b1434a2dfc2c4 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 20 Sep 2018 16:07:07 -0700 Subject: [PATCH 0052/1825] Resovle conflicts in BUILD file --- tensorflow/core/kernels/data/BUILD | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index 23ddf32be7..402ebd9e2b 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -703,6 +703,18 @@ tf_kernel_library( ], ) +tf_kernel_library( + name = "matching_files_dataset_op", + srcs = ["matching_files_dataset_op.cc"], + deps = [ + ":dataset", + "//tensorflow/core:dataset_ops_op_lib", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + ], +) + tf_kernel_library( name = "model_dataset_op", srcs = ["model_dataset_op.cc"], @@ -808,18 +820,4 @@ tf_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", ], -) - - -tf_kernel_library( - name = "matching_files_dataset_op", - srcs = ["matching_files_dataset_op.cc"], - deps = [ - ":dataset", - "//tensorflow/core:dataset_ops_op_lib", - "//tensorflow/core:framework", - "//tensorflow/core:lib", - "//tensorflow/core:lib_internal", - ], -) - +) \ No newline at end of file -- GitLab From 058cf57db6cd584b4c30e2f8ce80e61dc6190e7d Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 21 Sep 2018 21:57:51 -0700 Subject: [PATCH 0053/1825] Add a test case for shuffling --- .../matching_files_dataset_op_test.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index 73e5d3e4d1..a098ca6cb0 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -233,6 +233,33 @@ class MatchingFilesDatasetTest(test.TestCase): result.extend(filename) matched_filenames = [compat.as_bytes(x) for x in result] + self.assertEqual(matched_filenames, test_filenames) + + def testShuffle(self): + self.maxDiff = None + base, test_filenames = self._load_data() + test_filenames.sort(reverse=True) + patterns = array_ops.placeholder(dtypes.string, shape=[None]) + dataset = MatchingFilesDataset(patterns) + dataset = dataset.shuffle(buffer_size=10, reshuffle_each_iteration=False) + iterator = iterator_ops.Iterator.from_structure(dataset.output_types) + init_op = iterator.make_initializer(dataset) + get_next = iterator.get_next() + result = [] + with self.cached_session() as sess: + pattern = '{}/{}/*.txt' \ + .format(base, os.path.join(*['**' for _ in range(depth)])) + search_patterns = [pattern] + sess.run(init_op, feed_dict={patterns: search_patterns}) + result.extend(timeit(partial(self._read_data_with_result, get_next, sess), + "read first filename")) + result.extend(timeit(partial(self._read_data_with_result, get_next, sess), + "read second filename")) + N = width * len(search_patterns) - 2 + filename = timeit(partial(self._read_data_with_result, get_next, sess, N), + 'read {} more filenames'.format(N), N) + result.extend(filename) + matched_filenames = [compat.as_bytes(x) for x in result] self.assertItemsEqual(matched_filenames, test_filenames) -- GitLab From 7496aa779843a94f1ad9868d69ec4adaea84295b Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 26 Sep 2018 08:06:06 -0700 Subject: [PATCH 0054/1825] Update the iterator construction and add the test for nested directories and a benchmark test --- tensorflow/python/data/kernel_tests/BUILD | 1 + .../matching_files_dataset_op_test.py | 325 ++++++++---------- 2 files changed, 143 insertions(+), 183 deletions(-) diff --git a/tensorflow/python/data/kernel_tests/BUILD b/tensorflow/python/data/kernel_tests/BUILD index 89b5fde727..863d848c98 100644 --- a/tensorflow/python/data/kernel_tests/BUILD +++ b/tensorflow/python/data/kernel_tests/BUILD @@ -336,6 +336,7 @@ tf_py_test( size = "small", srcs = ["matching_files_dataset_op_test.py"], additional_deps = [ + "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index a098ca6cb0..4a62a31144 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -1,4 +1,4 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# 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. @@ -17,47 +17,19 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from os import path +import os import shutil import tempfile +import time + +import numpy as np -from tensorflow.python.data.ops import iterator_ops +from tensorflow.python.client import session from tensorflow.python.data.ops.dataset_ops import MatchingFilesDataset -from tensorflow.python.framework import dtypes -from tensorflow.python.ops import array_ops +from tensorflow.python.framework import errors +from tensorflow.python.framework import ops from tensorflow.python.platform import test from tensorflow.python.util import compat -from tensorflow.python.data.ops import dataset_ops -from tensorflow.python.ops.gen_io_ops import matching_files -from tensorflow.python.framework import errors - -import os -import time -from functools import partial - - -try: - import psutil # pylint: disable=g-import-not-at-top - - psutil_import_succeeded = True -except ImportError: - psutil_import_succeeded = False - - -def timeit(fn, msg, N=0): - start = time.time() - res = fn() - end = time.time() - runtime = (end - start) * 1000 - msg = '{}: time: {:.2f} ms'.format(msg, runtime) - if N: - msg += ' ({:.2f} ms per iteration)'.format(runtime / N) - print(msg) - return res - - -width = 1000 -depth = 20 class MatchingFilesDatasetTest(test.TestCase): @@ -70,15 +42,12 @@ class MatchingFilesDatasetTest(test.TestCase): def _touchTempFiles(self, filenames): for filename in filenames: - open(path.join(self.tmp_dir, filename), 'a').close() + open(os.path.join(self.tmp_dir, filename), 'a').close() def testEmptyDirectory(self): - dataset = MatchingFilesDataset(path.join(self.tmp_dir, '*')) + dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) with self.cached_session() as sess: - itr = iterator_ops.Iterator.from_structure(dataset.output_types) - init_op = itr.make_initializer(dataset) - next_element = itr.get_next() - sess.run(init_op) + next_element = dataset.make_one_shot_iterator().get_next() with self.assertRaises(errors.OutOfRangeError): sess.run(next_element) @@ -86,181 +55,171 @@ class MatchingFilesDatasetTest(test.TestCase): filenames = ['a', 'b', 'c'] self._touchTempFiles(filenames) - dataset = MatchingFilesDataset(path.join(self.tmp_dir, '*')) + dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) with self.cached_session() as sess: - itr = iterator_ops.Iterator.from_structure(dataset.output_types) - init_op = itr.make_initializer(dataset) - next_element = itr.get_next() - sess.run(init_op) + next_element = dataset.make_one_shot_iterator().get_next() - full_filenames = [] - produced_filenames = [] + expected_filenames = [] + actual_filenames = [] for filename in filenames: - full_filenames.append( - compat.as_bytes(path.join(self.tmp_dir, filename))) - produced_filenames.append(compat.as_bytes(sess.run(next_element))) - self.assertItemsEqual(full_filenames, produced_filenames) + expected_filenames.append( + compat.as_bytes(os.path.join(self.tmp_dir, filename))) + actual_filenames.append(compat.as_bytes(sess.run(next_element))) + + self.assertItemsEqual(expected_filenames, actual_filenames) with self.assertRaises(errors.OutOfRangeError): - sess.run(itr.get_next()) + sess.run(next_element) def testSimpleDirectoryInitializer(self): filenames = ['a', 'b', 'c'] self._touchTempFiles(filenames) - filename_placeholder = array_ops.placeholder(dtypes.string, shape=[]) - dataset = MatchingFilesDataset(filename_placeholder) - + dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) with self.cached_session() as sess: - itr = iterator_ops.Iterator.from_structure(dataset.output_types) - init_op = itr.make_initializer(dataset) - next_element = itr.get_next() - sess.run( - init_op, - feed_dict={filename_placeholder: path.join(self.tmp_dir, '*')}) - - full_filenames = [] - produced_filenames = [] + next_element = dataset.make_one_shot_iterator().get_next() + expected_filenames = [] + actual_filenames = [] for filename in filenames: - full_filenames.append( - compat.as_bytes(path.join(self.tmp_dir, filename))) - produced_filenames.append(compat.as_bytes(sess.run(next_element))) - - self.assertItemsEqual(full_filenames, produced_filenames) + expected_filenames.append( + compat.as_bytes(os.path.join(self.tmp_dir, filename))) + actual_filenames.append(compat.as_bytes(sess.run(next_element))) + self.assertItemsEqual(expected_filenames, actual_filenames) with self.assertRaises(errors.OutOfRangeError): - sess.run(itr.get_next()) + sess.run(next_element) def testFileSuffixes(self): filenames = ['a.txt', 'b.py', 'c.py', 'd.pyc'] self._touchTempFiles(filenames) - filename_placeholder = array_ops.placeholder(dtypes.string, shape=[]) - dataset = MatchingFilesDataset(filename_placeholder) - + dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*.py')) with self.cached_session() as sess: - itr = iterator_ops.Iterator.from_structure(dataset.output_types) - init_op = itr.make_initializer(dataset) - next_element = itr.get_next() - sess.run( - init_op, - feed_dict={filename_placeholder: path.join(self.tmp_dir, '*.py')}) - - full_filenames = [] - produced_filenames = [] + next_element = dataset.make_one_shot_iterator().get_next() + expected_filenames = [] + actual_filenames = [] for filename in filenames[1:-1]: - full_filenames.append( - compat.as_bytes(path.join(self.tmp_dir, filename))) - produced_filenames.append(compat.as_bytes(sess.run(next_element))) - self.assertItemsEqual(full_filenames, produced_filenames) + expected_filenames.append( + compat.as_bytes(os.path.join(self.tmp_dir, filename))) + actual_filenames.append(compat.as_bytes(sess.run(next_element))) + self.assertItemsEqual(expected_filenames, actual_filenames) with self.assertRaises(errors.OutOfRangeError): - sess.run(itr.get_next()) + sess.run(next_element) def testFileMiddles(self): filenames = ['a.txt', 'b.py', 'c.pyc'] self._touchTempFiles(filenames) - filename_placeholder = array_ops.placeholder(dtypes.string, shape=[]) - dataset = MatchingFilesDataset(filename_placeholder) - + dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*.py*')) with self.cached_session() as sess: - itr = iterator_ops.Iterator.from_structure(dataset.output_types) - init_op = itr.make_initializer(dataset) - next_element = itr.get_next() - sess.run( - init_op, - feed_dict={filename_placeholder: path.join(self.tmp_dir, '*.py*')}) - - full_filenames = [] - produced_filenames = [] + next_element = dataset.make_one_shot_iterator().get_next() + expected_filenames = [] + actual_filenames = [] for filename in filenames[1:]: - full_filenames.append( - compat.as_bytes(path.join(self.tmp_dir, filename))) - produced_filenames.append(compat.as_bytes(sess.run(next_element))) - - self.assertItemsEqual(full_filenames, produced_filenames) + expected_filenames.append( + compat.as_bytes(os.path.join(self.tmp_dir, filename))) + actual_filenames.append(compat.as_bytes(sess.run(next_element))) + self.assertItemsEqual(expected_filenames, actual_filenames) with self.assertRaises(errors.OutOfRangeError): - sess.run(itr.get_next()) - - def _load_data(self): - new_files = [] - dir = "/tmp/test/" - if not os.path.exists(dir): - os.makedirs(dir) - base = tempfile.mkdtemp(prefix=dir) - print('saving files to dir: {}'.format(base)) + sess.run(next_element) + + def testNestedDirectories(self): + filenames = [] + width = 8 + depth = 4 for i in range(width): - new_base = os.path.join(base, str(i), *[str(j) for j in range(depth - 1)]) - if not os.path.exists(new_base): - os.makedirs(new_base) - f = os.path.join(new_base, 'stuff.txt') - new_files.append(compat.as_bytes(f)) - open(f, 'w').close() - return base, new_files - - def _read_data(self, data, sess, N=1): - for _ in range(N): - sess.run(data) - - def _read_data_with_result(self, data, sess, N=1): - result = [] - for _ in range(N): - result.append(sess.run(data)) - return result - - def testPerformance(self): - base, test_filenames = self._load_data() - test_filenames.sort(reverse=True) - patterns = array_ops.placeholder(dtypes.string, shape=[None]) - dataset = MatchingFilesDataset(patterns) - iterator = iterator_ops.Iterator.from_structure(dataset.output_types) - init_op = iterator.make_initializer(dataset) - get_next = iterator.get_next() - result = [] - with self.cached_session() as sess: - pattern = '{}/{}/*.txt'\ - .format(base, os.path.join(*['**' for _ in range(depth)])) - search_patterns = [pattern] - sess.run(init_op, feed_dict={patterns: search_patterns}) - result.extend(timeit(partial(self._read_data_with_result, get_next, sess), - "read first filename")) - result.extend(timeit(partial(self._read_data_with_result, get_next, sess), - "read second filename")) - N = width * len(search_patterns) - 2 - filename = timeit(partial(self._read_data_with_result, get_next, sess, N), - 'read {} more filenames'.format(N), N) - result.extend(filename) - - matched_filenames = [compat.as_bytes(x) for x in result] - self.assertEqual(matched_filenames, test_filenames) - - def testShuffle(self): - self.maxDiff = None - base, test_filenames = self._load_data() - test_filenames.sort(reverse=True) - patterns = array_ops.placeholder(dtypes.string, shape=[None]) + for j in range(depth): + new_base = os.path.join(self.tmp_dir, str(i), + *[str(dir_name) for dir_name in range(j)]) + os.makedirs(new_base, exist_ok=True) + for f in ['a.txt', 'b.py', 'c.pyc']: + filename = os.path.join(new_base, f) + filenames.append(filename) + open(filename, 'w').close() + + patterns = [] + for i in range(depth): + pattern = '{}/{}/*.txt'.format( + self.tmp_dir, os.path.join(*['**' for _ in range(i + 1)])) + patterns.append(pattern) + dataset = MatchingFilesDataset(patterns) - dataset = dataset.shuffle(buffer_size=10, reshuffle_each_iteration=False) - iterator = iterator_ops.Iterator.from_structure(dataset.output_types) - init_op = iterator.make_initializer(dataset) - get_next = iterator.get_next() - result = [] with self.cached_session() as sess: - pattern = '{}/{}/*.txt' \ - .format(base, os.path.join(*['**' for _ in range(depth)])) - search_patterns = [pattern] - sess.run(init_op, feed_dict={patterns: search_patterns}) - result.extend(timeit(partial(self._read_data_with_result, get_next, sess), - "read first filename")) - result.extend(timeit(partial(self._read_data_with_result, get_next, sess), - "read second filename")) - N = width * len(search_patterns) - 2 - filename = timeit(partial(self._read_data_with_result, get_next, sess, N), - 'read {} more filenames'.format(N), N) - result.extend(filename) - matched_filenames = [compat.as_bytes(x) for x in result] - self.assertItemsEqual(matched_filenames, test_filenames) + next_element = dataset.make_one_shot_iterator().get_next() + expected_filenames = [compat.as_bytes(file) + for file in filenames if file.endswith('.txt')] + actual_filenames = [] + while True: + try: + actual_filenames.append(compat.as_bytes(sess.run(next_element))) + except errors.OutOfRangeError: + break + + self.assertItemsEqual(expected_filenames, actual_filenames) + + +class MatchingFilesDatasetBenchmark(test.Benchmark): + + def benchmarkNestedDirectories(self): + tmp_dir = tempfile.mkdtemp() + width = 1000 + depth = 10 + for i in range(width): + for j in range(depth): + new_base = os.path.join(tmp_dir, str(i), + *[str(dir_name) for dir_name in range(j)]) + if not os.path.exists(new_base): + os.makedirs(new_base) + for f in ['a.txt', 'b.py', 'c.pyc']: + filename = os.path.join(new_base, f) + open(filename, 'w').close() + + patterns = [] + for i in range(depth): + pattern = '{}/{}/*.txt'.format(tmp_dir, + os.path.join(*['**' for _ in range(i + 1)])) + patterns.append(pattern) + + deltas = [] + iters = 3 + for _ in range(iters): + with ops.Graph().as_default(): + dataset = MatchingFilesDataset(patterns) + next_element = dataset.make_one_shot_iterator().get_next() + + with session.Session() as sess: + sub_deltas = [] + while True: + try: + start = time.time() + sess.run(next_element) + end = time.time() + sub_deltas.append(end - start) + except errors.OutOfRangeError: + 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), + extras={"read first file:": median_deltas[0], + "read second file:": median_deltas[1], + "avg time for reading %d more filenames:" % + (len(median_deltas) - 2): + np.average(median_deltas[2:])}, + name="benchmark_matching_files_dataset_nesteddirectory(%d*%d)" % + (width, depth)) + + shutil.rmtree(tmp_dir, ignore_errors=True) if __name__ == "__main__": -- GitLab From 453bed8e4b91431765c138e7d09c1419ea9588a8 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 26 Sep 2018 08:09:46 -0700 Subject: [PATCH 0055/1825] Improve the kernel implementation (missing newline, ForEach, comment format) --- tensorflow/core/kernels/data/BUILD | 2 +- .../kernels/data/matching_files_dataset_op.cc | 119 ++++++++---------- 2 files changed, 50 insertions(+), 71 deletions(-) diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD index 402ebd9e2b..59c20033b0 100644 --- a/tensorflow/core/kernels/data/BUILD +++ b/tensorflow/core/kernels/data/BUILD @@ -820,4 +820,4 @@ tf_kernel_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", ], -) \ No newline at end of file +) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index b43517d0f5..d608d9d43c 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -1,4 +1,4 @@ -/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. +/* 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. @@ -31,30 +31,10 @@ limitations under the License. namespace tensorflow { namespace data { - namespace { -constexpr int kNumThreads = 8; - -// Run a function in parallel using a ThreadPool, but skip the ThreadPool -// on the iOS platform due to its problems with more than a few threads. -void ForEach(int first, int last, const std::function& f) { -#if TARGET_OS_IPHONE - for (int i = first; i < last; i++) { - f(i); - } -#else - int num_threads = std::min(kNumThreads, last - first); - thread::ThreadPool threads(Env::Default(), "ForEach", num_threads); - for (int i = first; i < last; i++) { - threads.Schedule([f, i] { f(i); }); - } -#endif -} - -} // namespace - -namespace { +// See documentation in ../ops/dataset_ops.cc for a high-level +// description of the following op. class MatchingFilesDatasetOp : public DatasetOpKernel { public: @@ -62,16 +42,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { void MakeDataset(OpKernelContext* ctx, DatasetBase** output) override { const Tensor* patterns_t; - // NOTE(originally from ringwalt): Changing the input name "pattern" to - // "patterns" would break existing graphs. - OP_REQUIRES_OK(ctx, ctx->input("pattern", &patterns_t)); - OP_REQUIRES( - ctx, - TensorShapeUtils::IsScalar(patterns_t->shape()) || - TensorShapeUtils::IsVector(patterns_t->shape()), - errors::InvalidArgument( - "Input patterns tensor must be scalar or vector, but had shape: ", - patterns_t->shape().DebugString())); + OP_REQUIRES_OK(ctx, ctx->input("patterns", &patterns_t)); const auto patterns = patterns_t->flat(); size_t num_patterns = static_cast(patterns.size()); std::vector pattern_strs; @@ -81,8 +52,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { pattern_strs.push_back(patterns(i)); } - // keep the elements in the descending order - std::sort(pattern_strs.begin(), pattern_strs.end(), std::greater()); + // keep the elements in the ascending order + std::sort(pattern_strs.begin(), pattern_strs.end()); *output = new Dataset(ctx, std::move(pattern_strs)); } @@ -96,7 +67,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::unique_ptr MakeIteratorInternal( const string& prefix) const override { return std::unique_ptr( - new Iterator({this, strings::StrCat(prefix, "::FileName")})); + new Iterator({this, strings::StrCat(prefix, "::MatchingFiles")})); } const DataTypeVector& output_dtypes() const override { @@ -118,9 +89,9 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { Status AsGraphDefInternal(SerializationContext* ctx, DatasetGraphDefBuilder* b, Node** output) const override { - Node* pattern = nullptr; - TF_RETURN_IF_ERROR(b->AddVector(pattern_, &pattern)); - TF_RETURN_IF_ERROR(b->AddDataset(this, {pattern}, output)); + Node* patterns_node = nullptr; + TF_RETURN_IF_ERROR(b->AddVector(pattern_, &patterns_node)); + TF_RETURN_IF_ERROR(b->AddDataset(this, {patterns_node}, output)); return Status::OK(); } @@ -138,14 +109,14 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { while (!filepath_queue_.empty() || current_pattern_index_ < dataset()->pattern_.size()) { - // all the elements in the heap will be the matched file name or the - // potential directory + // All the elements in the heap will be the matched filename or the + // potential directory. if (!filepath_queue_.empty()) { string cur_file = filepath_queue_.top(); filepath_queue_.pop(); - // we can also use isDectory() here. But IsDirectory call can be - // expensive for some FS + // We can also use isDectory() here. But IsDirectory call can be + // expensive for some FS. if (ctx->env()->MatchPath(cur_file, current_pattern_)){ Tensor filepath_tensor(ctx->allocator({}), DT_STRING, {}); filepath_tensor.scalar()() = cur_file; @@ -154,20 +125,20 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { return Status::OK(); } - // in this case, cur_file is a directory. Then create a sub-pattern - // to continue the search + // In this case, cur_file is a directory. Then create a sub-pattern + // to continue the search. size_t pos = current_pattern_.find_first_of("*?[\\"); size_t len = current_pattern_.size() - pos; string cur_pattern_suffix = current_pattern_.substr(pos, len); string sub_pattern = strings::StrCat(cur_file, "/", cur_pattern_suffix); - Status s = UpdateIterator(ctx->env(), sub_pattern); + Status s = UpdateIterator(ctx, sub_pattern); ret.Update(s); } else { // search a new pattern current_pattern_ = dataset()->pattern_[current_pattern_index_]; - Status s = UpdateIterator(ctx->env(), current_pattern_); + Status s = UpdateIterator(ctx, current_pattern_); ret.Update(s); ++current_pattern_index_; } @@ -224,14 +195,14 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } private: - Status UpdateIterator(Env *env, const string &pattern) + Status UpdateIterator(IteratorContext* ctx, const string &pattern) EXCLUSIVE_LOCKS_REQUIRED(mu_) { string fixed_prefix = pattern.substr(0, pattern.find_first_of("*?[\\")); string eval_pattern = pattern; string dir(io::Dirname(fixed_prefix)); - // If dir is empty then we need to fix up fixed_prefix and eval_pattern to - // include . as the top level directory. + // If dir is empty then we need to fix up fixed_prefix and eval_pattern + // to include . as the top level directory. if (dir.empty()) { dir = "."; fixed_prefix = io::JoinPath(dir, fixed_prefix); @@ -239,16 +210,16 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } FileSystem* fs; - TF_RETURN_IF_ERROR(env->GetFileSystemForFile(dir, &fs)); + TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile(dir, &fs)); filepath_queue_.push(dir); Status ret; //Status to return - // children_dir_status holds is_dir status for children. It can have three - // possible values: OK for true; FAILED_PRECONDITION for false; CANCELLED - // if we don't calculate IsDirectory (we might do that because there isn't - // any point in exploring that child path). + // children_dir_status holds is_dir status for children. It can have + // three possible values: OK for true; FAILED_PRECONDITION for false; + // CANCELLED if we don't calculate IsDirectory (we might do that because + // there isn't any point in exploring that child path). - // DFS to find the first element in the iterator + // DFS to find the first element in the iterator. while (!filepath_queue_.empty()) { string cur_dir = filepath_queue_.top(); filepath_queue_.pop(); @@ -256,14 +227,14 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { Status s = fs->GetChildren(cur_dir, &children); ret.Update(s); - // if cur_dir has no children, there will two possible situations: 1) + // If cur_dir has no children, there will two possible situations: 1) // the cur_dir is an empty dir; 2) the cur_dir is actual a file // instead of a director. For the first one, continue to search the - // heap; For the second one, if the file matches the pattern, add + // heap. For the second one, if the file matches the pattern, add // it to the heap and finish the search; otherwise, continue the next - // search + // search. if (children.empty()) { - if (env->MatchPath(cur_dir, current_pattern_)) { + if (ctx->env()->MatchPath(cur_dir, eval_pattern)) { filepath_queue_.push(cur_dir); return ret; } else { @@ -272,13 +243,14 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } std::map children_dir_status; - // This IsDirectory call can be expensive for some FS. Parallelizing it. - ForEach(0, children.size(), + // This IsDirectory call can be expensive for some FS. Parallelizing + // it. + ForEach(ctx, 0, children.size(), [fs, &cur_dir, &children, &fixed_prefix, &children_dir_status] (int i) { const string child_path = io::JoinPath(cur_dir, children[i]); - // In case the child_path doesn't start with the fixed_prefix then - // we don't need to explore this path. + // In case the child_path doesn't start with the + // fixed_prefix, then we don't need to explore this path. if (!str_util::StartsWith(child_path, fixed_prefix)) { children_dir_status[child_path] = Status(tensorflow::error::CANCELLED, @@ -300,9 +272,9 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { //push the child dir for next search filepath_queue_.push(child_dir_path); } else { - // this case will be a file; if the file match the pattern, push - // it to the heap; otherwise, ignore it - if (env->MatchPath(child_dir_path, eval_pattern)) { + // This case will be a file: if the file matches the pattern, push + // it to the heap; otherwise, ignore it. + if (ctx->env()->MatchPath(child_dir_path, eval_pattern)) { filepath_queue_.push(child_dir_path); } } @@ -311,9 +283,16 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { return ret; } + static void ForEach(IteratorContext* ctx, int first, int last, + const std::function& f) { + for (int i = first; i < last ; i++) { + (*ctx->runner())([f, i] {std::bind(f, i);}); + } + } + mutex mu_; - //std::unique_ptr> filepath_queue_ GUARDED_BY(mu_); - std::priority_queue filepath_queue_ GUARDED_BY(mu_); // = new std::priority_queue; + std::priority_queue, std::less> + filepath_queue_ GUARDED_BY(mu_); size_t current_pattern_index_ GUARDED_BY(mu_) = 0; string current_pattern_ GUARDED_BY(mu_); }; @@ -327,4 +306,4 @@ REGISTER_KERNEL_BUILDER(Name("MatchingFilesDataset").Device(DEVICE_CPU), } // namespace } // namespace data -} // namespace tensorflow \ No newline at end of file +} // namespace tensorflow -- GitLab From 7ef0ddfd06a799220dfd4989ed8067036efcdcb5 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 26 Sep 2018 08:11:02 -0700 Subject: [PATCH 0056/1825] change pattern to be patterns in Python API --- tensorflow/core/ops/dataset_ops.cc | 2 +- tensorflow/python/data/ops/dataset_ops.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/ops/dataset_ops.cc b/tensorflow/core/ops/dataset_ops.cc index 26c2756836..14596e7f4e 100644 --- a/tensorflow/core/ops/dataset_ops.cc +++ b/tensorflow/core/ops/dataset_ops.cc @@ -620,7 +620,7 @@ REGISTER_OP("TextLineDataset") }); REGISTER_OP("MatchingFilesDataset") - .Input("pattern: string") + .Input("patterns: string") .Output("handle: variant") .SetIsStateful() // TODO(b/65524810): Source dataset ops must be marked // stateful to inhibit constant folding. diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 5150c7fb9a..063700e335 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2653,16 +2653,16 @@ class MapDataset(UnaryDataset): class MatchingFilesDataset(Dataset): - """A `Dataset` that list the files according to the input patterns""" + """A `Dataset` that list the files according to the input patterns.""" - def __init__(self, pattern): + def __init__(self, patterns): super(MatchingFilesDataset, self).__init__() - self._pattern = ops.convert_to_tensor( - pattern, dtype=dtypes.string, name="pattern") + self._patterns = ops.convert_to_tensor( + patterns, dtype=dtypes.string, name="patterns") def _as_variant_tensor(self): - return gen_dataset_ops.matching_files_dataset(self._pattern) + return gen_dataset_ops.matching_files_dataset(self._patterns) @property def output_classes(self): -- GitLab From 17e093dffe0d48563c40ce3a1bbfc998b0777689 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 26 Sep 2018 10:48:41 -0700 Subject: [PATCH 0057/1825] Address the coding style issues --- .../kernels/data/matching_files_dataset_op.cc | 90 +++++++++---------- .../matching_files_dataset_op_test.py | 45 ++++------ 2 files changed, 62 insertions(+), 73 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index d608d9d43c..ced8b304d7 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -13,21 +13,21 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include +#include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/partial_tensor_shape.h" #include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/kernels/data/dataset.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/lib/core/threadpool.h" #include "tensorflow/core/lib/io/buffered_inputstream.h" #include "tensorflow/core/lib/io/inputbuffer.h" +#include "tensorflow/core/lib/io/path.h" #include "tensorflow/core/lib/io/random_inputstream.h" #include "tensorflow/core/lib/io/record_reader.h" #include "tensorflow/core/lib/io/zlib_compression_options.h" #include "tensorflow/core/lib/io/zlib_inputstream.h" -#include "tensorflow/core/framework/op_kernel.h" -#include "tensorflow/core/framework/tensor_shape.h" -#include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/platform/env.h" -#include "tensorflow/core/lib/io/path.h" -#include "tensorflow/core/lib/core/threadpool.h" namespace tensorflow { namespace data { @@ -44,7 +44,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { const Tensor* patterns_t; OP_REQUIRES_OK(ctx, ctx->input("patterns", &patterns_t)); const auto patterns = patterns_t->flat(); - size_t num_patterns = static_cast(patterns.size()); + size_t num_patterns = static_cast(patterns.size()); std::vector pattern_strs; pattern_strs.reserve(num_patterns); @@ -61,8 +61,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { class Dataset : public DatasetBase { public: Dataset(OpKernelContext* ctx, std::vector patterns) - : DatasetBase(DatasetContext(ctx)), - pattern_(std::move(patterns)) {} + : DatasetBase(DatasetContext(ctx)), pattern_(std::move(patterns)) {} std::unique_ptr MakeIteratorInternal( const string& prefix) const override { @@ -108,7 +107,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { Status ret; while (!filepath_queue_.empty() || - current_pattern_index_ < dataset()->pattern_.size()) { + current_pattern_index_ < dataset()->pattern_.size()) { // All the elements in the heap will be the matched filename or the // potential directory. if (!filepath_queue_.empty()) { @@ -117,7 +116,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // We can also use isDectory() here. But IsDirectory call can be // expensive for some FS. - if (ctx->env()->MatchPath(cur_file, current_pattern_)){ + if (ctx->env()->MatchPath(cur_file, current_pattern_)) { Tensor filepath_tensor(ctx->allocator({}), DT_STRING, {}); filepath_tensor.scalar()() = cur_file; out_tensors->emplace_back(std::move(filepath_tensor)); @@ -130,9 +129,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { size_t pos = current_pattern_.find_first_of("*?[\\"); size_t len = current_pattern_.size() - pos; string cur_pattern_suffix = current_pattern_.substr(pos, len); - string sub_pattern = strings::StrCat(cur_file, - "/", - cur_pattern_suffix); + string sub_pattern = + strings::StrCat(cur_file, "/", cur_pattern_suffix); Status s = UpdateIterator(ctx, sub_pattern); ret.Update(s); } else { @@ -152,16 +150,14 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { Status SaveInternal(IteratorStateWriter* writer) override { mutex_lock l(mu_); TF_RETURN_IF_ERROR(writer->WriteScalar( - full_name("current_pattern_index"), - current_pattern_index_)); + full_name("current_pattern_index"), current_pattern_index_)); - TF_RETURN_IF_ERROR(writer->WriteScalar( - full_name("current_pattern"), - current_pattern_)); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("current_pattern"), + current_pattern_)); if (!filepath_queue_.empty()) { - TF_RETURN_IF_ERROR(writer->WriteScalar( - full_name("queue_size"), filepath_queue_.size())); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("queue_size"), + filepath_queue_.size())); for (int i = 0; i < filepath_queue_.size(); ++i) { TF_RETURN_IF_ERROR(writer->WriteScalar( full_name(strings::StrCat("queue_element_", i)), @@ -175,16 +171,16 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { IteratorStateReader* reader) override { mutex_lock l(mu_); int64 current_pattern_index; - TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("current_pattern_index"), - ¤t_pattern_index)); + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name("current_pattern_index"), ¤t_pattern_index)); current_pattern_index_ = size_t(current_pattern_index); TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("current_pattern"), ¤t_pattern_)); int64 queue_size; - TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("queue_size"), - &queue_size)); + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name("queue_size"), &queue_size)); for (int i = static_cast(queue_size - 1); i >= 0; --i) { string element; TF_RETURN_IF_ERROR(reader->ReadScalar( @@ -195,8 +191,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } private: - Status UpdateIterator(IteratorContext* ctx, const string &pattern) - EXCLUSIVE_LOCKS_REQUIRED(mu_) { + Status UpdateIterator(IteratorContext* ctx, const string& pattern) + EXCLUSIVE_LOCKS_REQUIRED(mu_) { string fixed_prefix = pattern.substr(0, pattern.find_first_of("*?[\\")); string eval_pattern = pattern; string dir(io::Dirname(fixed_prefix)); @@ -213,7 +209,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile(dir, &fs)); filepath_queue_.push(dir); - Status ret; //Status to return + Status ret; // Status to return // children_dir_status holds is_dir status for children. It can have // three possible values: OK for true; FAILED_PRECONDITION for false; // CANCELLED if we don't calculate IsDirectory (we might do that because @@ -245,22 +241,22 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::map children_dir_status; // This IsDirectory call can be expensive for some FS. Parallelizing // it. - ForEach(ctx, 0, children.size(), - [fs, &cur_dir, &children, &fixed_prefix, - &children_dir_status] (int i) { - const string child_path = io::JoinPath(cur_dir, children[i]); - // In case the child_path doesn't start with the - // fixed_prefix, then we don't need to explore this path. - if (!str_util::StartsWith(child_path, fixed_prefix)) { - children_dir_status[child_path] = - Status(tensorflow::error::CANCELLED, - "Operation not needed"); - } else { - children_dir_status[child_path] = fs->IsDirectory(child_path); - } - }); - - for (const auto &child : children) { + ForEach( + ctx, 0, children.size(), + [fs, &cur_dir, &children, &fixed_prefix, + &children_dir_status](int i) { + const string child_path = io::JoinPath(cur_dir, children[i]); + // In case the child_path doesn't start with the fixed_prefix, + // then we don't need to explore this path. + if (!str_util::StartsWith(child_path, fixed_prefix)) { + children_dir_status[child_path] = Status( + tensorflow::error::CANCELLED, "Operation not needed"); + } else { + children_dir_status[child_path] = fs->IsDirectory(child_path); + } + }); + + for (const auto& child : children) { const string child_dir_path = io::JoinPath(cur_dir, child); const Status child_dir_status = children_dir_status[child]; // If the IsDirectory call was cancelled we bail. @@ -269,7 +265,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } if (child_dir_status.ok()) { - //push the child dir for next search + // push the child dir for next search filepath_queue_.push(child_dir_path); } else { // This case will be a file: if the file matches the pattern, push @@ -284,9 +280,9 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } static void ForEach(IteratorContext* ctx, int first, int last, - const std::function& f) { - for (int i = first; i < last ; i++) { - (*ctx->runner())([f, i] {std::bind(f, i);}); + const std::function& f) { + for (int i = first; i < last; i++) { + (*ctx->runner())([f, i] { std::bind(f, i); }); } } diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index 4a62a31144..a2c6b78256 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -45,6 +45,8 @@ class MatchingFilesDatasetTest(test.TestCase): open(os.path.join(self.tmp_dir, filename), 'a').close() def testEmptyDirectory(self): + """Test the matchingfiles dataset with an empty directory""" + dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) with self.cached_session() as sess: next_element = dataset.make_one_shot_iterator().get_next() @@ -52,36 +54,20 @@ class MatchingFilesDatasetTest(test.TestCase): sess.run(next_element) def testSimpleDirectory(self): - filenames = ['a', 'b', 'c'] - self._touchTempFiles(filenames) + """Test the matchingfiles dataset with a simple directory""" - dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) - with self.cached_session() as sess: - next_element = dataset.make_one_shot_iterator().get_next() - - expected_filenames = [] - actual_filenames = [] - for filename in filenames: - expected_filenames.append( - compat.as_bytes(os.path.join(self.tmp_dir, filename))) - actual_filenames.append(compat.as_bytes(sess.run(next_element))) - - self.assertItemsEqual(expected_filenames, actual_filenames) - with self.assertRaises(errors.OutOfRangeError): - sess.run(next_element) - - def testSimpleDirectoryInitializer(self): filenames = ['a', 'b', 'c'] self._touchTempFiles(filenames) dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) with self.cached_session() as sess: next_element = dataset.make_one_shot_iterator().get_next() + expected_filenames = [] actual_filenames = [] for filename in filenames: expected_filenames.append( - compat.as_bytes(os.path.join(self.tmp_dir, filename))) + compat.as_bytes(os.path.join(self.tmp_dir, filename))) actual_filenames.append(compat.as_bytes(sess.run(next_element))) self.assertItemsEqual(expected_filenames, actual_filenames) @@ -89,6 +75,8 @@ class MatchingFilesDatasetTest(test.TestCase): sess.run(next_element) def testFileSuffixes(self): + """Test the matchingfiles dataset using the suffixes of filename""" + filenames = ['a.txt', 'b.py', 'c.py', 'd.pyc'] self._touchTempFiles(filenames) @@ -99,7 +87,7 @@ class MatchingFilesDatasetTest(test.TestCase): actual_filenames = [] for filename in filenames[1:-1]: expected_filenames.append( - compat.as_bytes(os.path.join(self.tmp_dir, filename))) + compat.as_bytes(os.path.join(self.tmp_dir, filename))) actual_filenames.append(compat.as_bytes(sess.run(next_element))) self.assertItemsEqual(expected_filenames, actual_filenames) @@ -107,6 +95,8 @@ class MatchingFilesDatasetTest(test.TestCase): sess.run(next_element) def testFileMiddles(self): + """Test the matchingfiles dataset using the middles of filename""" + filenames = ['a.txt', 'b.py', 'c.pyc'] self._touchTempFiles(filenames) @@ -117,7 +107,7 @@ class MatchingFilesDatasetTest(test.TestCase): actual_filenames = [] for filename in filenames[1:]: expected_filenames.append( - compat.as_bytes(os.path.join(self.tmp_dir, filename))) + compat.as_bytes(os.path.join(self.tmp_dir, filename))) actual_filenames.append(compat.as_bytes(sess.run(next_element))) self.assertItemsEqual(expected_filenames, actual_filenames) @@ -125,13 +115,15 @@ class MatchingFilesDatasetTest(test.TestCase): sess.run(next_element) def testNestedDirectories(self): + """Test the matchingfiles dataset with nested directories""" + filenames = [] width = 8 depth = 4 for i in range(width): for j in range(depth): new_base = os.path.join(self.tmp_dir, str(i), - *[str(dir_name) for dir_name in range(j)]) + *[str(dir_name) for dir_name in range(j)]) os.makedirs(new_base, exist_ok=True) for f in ['a.txt', 'b.py', 'c.pyc']: filename = os.path.join(new_base, f) @@ -141,7 +133,7 @@ class MatchingFilesDatasetTest(test.TestCase): patterns = [] for i in range(depth): pattern = '{}/{}/*.txt'.format( - self.tmp_dir, os.path.join(*['**' for _ in range(i + 1)])) + self.tmp_dir, os.path.join(*['**' for _ in range(i + 1)])) patterns.append(pattern) dataset = MatchingFilesDataset(patterns) @@ -168,7 +160,7 @@ class MatchingFilesDatasetBenchmark(test.Benchmark): for i in range(width): for j in range(depth): new_base = os.path.join(tmp_dir, str(i), - *[str(dir_name) for dir_name in range(j)]) + *[str(dir_name) for dir_name in range(j)]) if not os.path.exists(new_base): os.makedirs(new_base) for f in ['a.txt', 'b.py', 'c.pyc']: @@ -178,7 +170,8 @@ class MatchingFilesDatasetBenchmark(test.Benchmark): patterns = [] for i in range(depth): pattern = '{}/{}/*.txt'.format(tmp_dir, - os.path.join(*['**' for _ in range(i + 1)])) + os.path.join( + *['**' for _ in range(i + 1)])) patterns.append(pattern) deltas = [] @@ -217,7 +210,7 @@ class MatchingFilesDatasetBenchmark(test.Benchmark): (len(median_deltas) - 2): np.average(median_deltas[2:])}, name="benchmark_matching_files_dataset_nesteddirectory(%d*%d)" % - (width, depth)) + (width, depth)) shutil.rmtree(tmp_dir, ignore_errors=True) -- GitLab From 1f25931abcd036f2d0d94c368cd82ab02ffc5449 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 26 Sep 2018 11:28:03 -0700 Subject: [PATCH 0058/1825] Change pattern_ to patterns_ --- .../core/kernels/data/matching_files_dataset_op.cc | 10 +++++----- tensorflow/python/data/ops/dataset_ops.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index ced8b304d7..bfb893e856 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -61,7 +61,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { class Dataset : public DatasetBase { public: Dataset(OpKernelContext* ctx, std::vector patterns) - : DatasetBase(DatasetContext(ctx)), pattern_(std::move(patterns)) {} + : DatasetBase(DatasetContext(ctx)), patterns_(std::move(patterns)) {} std::unique_ptr MakeIteratorInternal( const string& prefix) const override { @@ -89,7 +89,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { DatasetGraphDefBuilder* b, Node** output) const override { Node* patterns_node = nullptr; - TF_RETURN_IF_ERROR(b->AddVector(pattern_, &patterns_node)); + TF_RETURN_IF_ERROR(b->AddVector(patterns_, &patterns_node)); TF_RETURN_IF_ERROR(b->AddDataset(this, {patterns_node}, output)); return Status::OK(); } @@ -107,7 +107,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { Status ret; while (!filepath_queue_.empty() || - current_pattern_index_ < dataset()->pattern_.size()) { + current_pattern_index_ < dataset()->patterns_.size()) { // All the elements in the heap will be the matched filename or the // potential directory. if (!filepath_queue_.empty()) { @@ -135,7 +135,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { ret.Update(s); } else { // search a new pattern - current_pattern_ = dataset()->pattern_[current_pattern_index_]; + current_pattern_ = dataset()->patterns_[current_pattern_index_]; Status s = UpdateIterator(ctx, current_pattern_); ret.Update(s); ++current_pattern_index_; @@ -293,7 +293,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { string current_pattern_ GUARDED_BY(mu_); }; - const std::vector pattern_; + const std::vector patterns_; }; }; diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 063700e335..d053902fc7 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2660,7 +2660,6 @@ class MatchingFilesDataset(Dataset): self._patterns = ops.convert_to_tensor( patterns, dtype=dtypes.string, name="patterns") - def _as_variant_tensor(self): return gen_dataset_ops.matching_files_dataset(self._patterns) -- GitLab From f6b2a32657a54e2faeefa34b9516ed5a43cb5530 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 26 Sep 2018 20:06:00 -0700 Subject: [PATCH 0059/1825] Update the RestoreInternal function since Max Heap is changed to Min Heap --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index bfb893e856..7bb8481762 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -181,7 +181,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { int64 queue_size; TF_RETURN_IF_ERROR( reader->ReadScalar(full_name("queue_size"), &queue_size)); - for (int i = static_cast(queue_size - 1); i >= 0; --i) { + for (int i = 0; i < queue_size; i++) { string element; TF_RETURN_IF_ERROR(reader->ReadScalar( full_name(strings::StrCat("queue_element_", i)), &element)); -- GitLab From 9972054a906bba9b1042410de224b570514cd9e7 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 27 Sep 2018 16:31:36 -0700 Subject: [PATCH 0060/1825] Update the test according to the recent refactoring of tf.data.tests --- tensorflow/python/data/kernel_tests/BUILD | 39 ++++++++----------- .../matching_files_dataset_op_test.py | 3 +- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/tensorflow/python/data/kernel_tests/BUILD b/tensorflow/python/data/kernel_tests/BUILD index 863d848c98..5a77538383 100644 --- a/tensorflow/python/data/kernel_tests/BUILD +++ b/tensorflow/python/data/kernel_tests/BUILD @@ -289,6 +289,22 @@ tf_py_test( ], ) +tf_py_test( + name = "matching_files_dataset_op_test", + size = "small", + srcs = ["matching_files_dataset_op_test.py"], + additional_deps = [ + ":test_base", + "//third_party/py/numpy", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:dtypes", + "//tensorflow/python:errors", + "//tensorflow/python:util", + "//tensorflow/python/data/ops:dataset_ops", + ], +) + cuda_py_test( name = "multi_device_iterator_test", size = "medium", @@ -331,29 +347,6 @@ cuda_py_test( ], ) -tf_py_test( - name = "matching_files_dataset_op_test", - size = "small", - srcs = ["matching_files_dataset_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:constant_op", - "//tensorflow/python:dataset_ops_gen", - "//tensorflow/python:dtypes", - "//tensorflow/python:errors", - "//tensorflow/python:framework_ops", - "//tensorflow/python:io_ops", - "//tensorflow/python:lib", - "//tensorflow/python:parsing_ops", - "//tensorflow/python:tensor_shape", - "//tensorflow/python:util", - "//tensorflow/python/data/ops:iterator_ops", - "//tensorflow/python/data/ops:readers", - ], -) - tf_py_test( name = "prefetch_dataset_op_test", size = "small", diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index a2c6b78256..37626355c2 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -25,6 +25,7 @@ import time import numpy as np from tensorflow.python.client import session +from tensorflow.python.data.kernel_tests import test_base from tensorflow.python.data.ops.dataset_ops import MatchingFilesDataset from tensorflow.python.framework import errors from tensorflow.python.framework import ops @@ -32,7 +33,7 @@ from tensorflow.python.platform import test from tensorflow.python.util import compat -class MatchingFilesDatasetTest(test.TestCase): +class MatchingFilesDatasetTest(test_base.DatasetTestBase): def setUp(self): self.tmp_dir = tempfile.mkdtemp() -- GitLab From 28836116a400a65eed9494390956033eee64c18d Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 28 Sep 2018 09:08:01 -0700 Subject: [PATCH 0061/1825] Minor updates on comments --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 7bb8481762..f052cdac52 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -33,7 +33,7 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../ops/dataset_ops.cc for a high-level +// See documentation in ../../ops/dataset_ops.cc for a high-level // description of the following op. class MatchingFilesDatasetOp : public DatasetOpKernel { @@ -48,7 +48,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::vector pattern_strs; pattern_strs.reserve(num_patterns); - for (int i = 0; i < num_patterns; ++i) { + for (int i = 0; i < num_patterns; i++) { pattern_strs.push_back(patterns(i)); } -- GitLab From 3fac3225211b337a4691c2498cc9b743f26dded8 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 28 Sep 2018 22:02:59 -0700 Subject: [PATCH 0062/1825] Fix the issues when parallelizing IsDirectory call --- .../kernels/data/matching_files_dataset_op.cc | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index f052cdac52..9f2b210871 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -28,6 +28,7 @@ limitations under the License. #include "tensorflow/core/lib/io/zlib_compression_options.h" #include "tensorflow/core/lib/io/zlib_inputstream.h" #include "tensorflow/core/platform/env.h" +#include "tensorflow/core/lib/core/blocking_counter.h" namespace tensorflow { namespace data { @@ -238,27 +239,36 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } } - std::map children_dir_status; + std::vector children_dir_status; + children_dir_status.resize(children.size()); + // This IsDirectory call can be expensive for some FS. Parallelizing // it. - ForEach( - ctx, 0, children.size(), - [fs, &cur_dir, &children, &fixed_prefix, - &children_dir_status](int i) { - const string child_path = io::JoinPath(cur_dir, children[i]); - // In case the child_path doesn't start with the fixed_prefix, - // then we don't need to explore this path. - if (!str_util::StartsWith(child_path, fixed_prefix)) { - children_dir_status[child_path] = Status( - tensorflow::error::CANCELLED, "Operation not needed"); - } else { - children_dir_status[child_path] = fs->IsDirectory(child_path); - } - }); - - for (const auto& child : children) { - const string child_dir_path = io::JoinPath(cur_dir, child); - const Status child_dir_status = children_dir_status[child]; + auto is_directory_fn = [fs, &cur_dir, &children, &fixed_prefix, + &children_dir_status](int i) { + const string child_path = io::JoinPath(cur_dir, children[i]); + // In case the child_path doesn't start with the fixed_prefix, then + // we don't need to explore this path. + if (!str_util::StartsWith(child_path, fixed_prefix)) { + children_dir_status[i] = Status( + tensorflow::error::CANCELLED, "Operation not needed"); + } else { + children_dir_status[i] = fs->IsDirectory(child_path); + } + }; + + BlockingCounter counter(children.size()); + for (int i = 0; i < children.size(); i++) { + (*ctx->runner())([&is_directory_fn, &counter, i] { + is_directory_fn(i); + counter.DecrementCount(); + }); + } + counter.Wait(); + + for (int i = 0; i < children.size(); i++) { + const string child_dir_path = io::JoinPath(cur_dir, children[i]); + const Status child_dir_status = children_dir_status[i]; // If the IsDirectory call was cancelled we bail. if (child_dir_status.code() == tensorflow::error::CANCELLED) { continue; @@ -279,13 +289,6 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { return ret; } - static void ForEach(IteratorContext* ctx, int first, int last, - const std::function& f) { - for (int i = first; i < last; i++) { - (*ctx->runner())([f, i] { std::bind(f, i); }); - } - } - mutex mu_; std::priority_queue, std::less> filepath_queue_ GUARDED_BY(mu_); -- GitLab From 5fd998c8876a5aac4eef71801280d3fc1c8f39a2 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 28 Sep 2018 22:06:02 -0700 Subject: [PATCH 0063/1825] Remvoe the comments that are no longer accurate --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 9f2b210871..4afd84f9c7 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -34,9 +34,6 @@ namespace tensorflow { namespace data { namespace { -// See documentation in ../../ops/dataset_ops.cc for a high-level -// description of the following op. - class MatchingFilesDatasetOp : public DatasetOpKernel { public: using DatasetOpKernel::DatasetOpKernel; -- GitLab From 0238bcdb51f68560ec1c3ca6469fe97953f36903 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 28 Sep 2018 22:20:32 -0700 Subject: [PATCH 0064/1825] Update the coding style --- .../kernel_tests/matching_files_dataset_op_test.py | 10 +++++----- tensorflow/python/data/ops/dataset_ops.py | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index 37626355c2..a000b9b816 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -46,7 +46,7 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): open(os.path.join(self.tmp_dir, filename), 'a').close() def testEmptyDirectory(self): - """Test the matchingfiles dataset with an empty directory""" + """Test the MatchingFiles dataset with an empty directory""" dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) with self.cached_session() as sess: @@ -55,7 +55,7 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): sess.run(next_element) def testSimpleDirectory(self): - """Test the matchingfiles dataset with a simple directory""" + """Test the MatchingFiles dataset with a simple directory""" filenames = ['a', 'b', 'c'] self._touchTempFiles(filenames) @@ -76,7 +76,7 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): sess.run(next_element) def testFileSuffixes(self): - """Test the matchingfiles dataset using the suffixes of filename""" + """Test the MatchingFiles dataset using the suffixes of filename""" filenames = ['a.txt', 'b.py', 'c.py', 'd.pyc'] self._touchTempFiles(filenames) @@ -96,7 +96,7 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): sess.run(next_element) def testFileMiddles(self): - """Test the matchingfiles dataset using the middles of filename""" + """Test the MatchingFiles dataset using the middles of filename""" filenames = ['a.txt', 'b.py', 'c.pyc'] self._touchTempFiles(filenames) @@ -116,7 +116,7 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): sess.run(next_element) def testNestedDirectories(self): - """Test the matchingfiles dataset with nested directories""" + """Test the MatchingFiles dataset with nested directories""" filenames = [] width = 8 diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index d053902fc7..05a7f5d41c 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2624,7 +2624,7 @@ class MapDataset(UnaryDataset): self._use_inter_op_parallelism = use_inter_op_parallelism wrapped_func = StructuredFunctionWrapper( - map_func, "Dataset.map()", input_dataset) + map_func, "Dataset.map()", input_dataset) self._output_classes = wrapped_func.output_classes self._output_shapes = wrapped_func.output_shapes self._output_types = wrapped_func.output_types @@ -2633,11 +2633,11 @@ class MapDataset(UnaryDataset): def _as_variant_tensor(self): input_t = self._input_dataset._as_variant_tensor() # pylint: disable=protected-access return gen_dataset_ops.map_dataset( - input_t, - self._map_func.captured_inputs, - f=self._map_func, - use_inter_op_parallelism=self._use_inter_op_parallelism, - **flat_structure(self)) + input_t, + self._map_func.captured_inputs, + f=self._map_func, + use_inter_op_parallelism=self._use_inter_op_parallelism, + **flat_structure(self)) @property def output_classes(self): @@ -2658,7 +2658,7 @@ class MatchingFilesDataset(Dataset): def __init__(self, patterns): super(MatchingFilesDataset, self).__init__() self._patterns = ops.convert_to_tensor( - patterns, dtype=dtypes.string, name="patterns") + patterns, dtype=dtypes.string, name="patterns") def _as_variant_tensor(self): return gen_dataset_ops.matching_files_dataset(self._patterns) -- GitLab From d87024b2e94e9dfab751ddf543c192d1a4f0f3c7 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 28 Sep 2018 22:30:23 -0700 Subject: [PATCH 0065/1825] Add the API defination pbtxt --- .../core/api_def/base_api/api_def_MatchingFilesDataset.pbtxt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 tensorflow/core/api_def/base_api/api_def_MatchingFilesDataset.pbtxt diff --git a/tensorflow/core/api_def/base_api/api_def_MatchingFilesDataset.pbtxt b/tensorflow/core/api_def/base_api/api_def_MatchingFilesDataset.pbtxt new file mode 100644 index 0000000000..ab2a33108d --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_MatchingFilesDataset.pbtxt @@ -0,0 +1,4 @@ +op { + graph_op_name: "MatchingFilesDataset" + visibility: HIDDEN +} -- GitLab From c03ab93355af474cba2eeec95be355b923185d20 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Sun, 30 Sep 2018 12:58:34 -0700 Subject: [PATCH 0066/1825] Add the serialization test --- ...tching_files_dataset_serialization_test.py | 65 +++++++++++++++++++ .../kernel_tests/serialization/BUILD | 12 ++++ 2 files changed, 77 insertions(+) create mode 100644 tensorflow/contrib/data/python/kernel_tests/serialization/matching_files_dataset_serialization_test.py diff --git a/tensorflow/contrib/data/python/kernel_tests/serialization/matching_files_dataset_serialization_test.py b/tensorflow/contrib/data/python/kernel_tests/serialization/matching_files_dataset_serialization_test.py new file mode 100644 index 0000000000..41926f7ae1 --- /dev/null +++ b/tensorflow/contrib/data/python/kernel_tests/serialization/matching_files_dataset_serialization_test.py @@ -0,0 +1,65 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for the MatchingFilesDataset serialization.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import shutil +import tempfile + +from tensorflow.contrib.data.python.kernel_tests.serialization import dataset_serialization_test_base +from tensorflow.python.data.ops.dataset_ops import MatchingFilesDataset +from tensorflow.python.platform import test + + +class MatchingFilesDatasetSerializationTest( + dataset_serialization_test_base.DatasetSerializationTestBase): + + def _build_iterator_graph(self, test_patterns): + return MatchingFilesDataset(test_patterns) + + def testMatchingFilesCore(self): + tmp_dir = tempfile.mkdtemp() + width = 16 + depth = 8 + for i in range(width): + for j in range(depth): + new_base = os.path.join(tmp_dir, str(i), + *[str(dir_name) for dir_name in range(j)]) + if not os.path.exists(new_base): + os.makedirs(new_base) + for f in ['a.txt', 'b.py', 'c.pyc']: + filename = os.path.join(new_base, f) + open(filename, 'w').close() + + patterns = [] + for i in range(depth): + pattern = '{}/{}/*.txt'.format(tmp_dir, + os.path.join( + *['**' for _ in range(i + 1)])) + patterns.append(pattern) + + num_outputs = width * depth + self.run_core_tests( + lambda: self._build_iterator_graph(patterns), + lambda: self._build_iterator_graph(patterns[0:depth // 2]), num_outputs) + + shutil.rmtree(tmp_dir, ignore_errors=True) + + +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 e556b65b7c..e8101c038a 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD @@ -332,6 +332,18 @@ py_test( ], ) +py_test( + name = "matching_files_dataset_serialization_test", + size = "small", + srcs = ["matching_files_dataset_serialization_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], +) + py_test( name = "optimize_dataset_serialization_test", size = "small", -- GitLab From 89d05bfa83f0adc1563c035c99e7f8d0ce58627f Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Sun, 30 Sep 2018 13:20:56 -0700 Subject: [PATCH 0067/1825] Resolve several issues in MatchingFiles Dataset kernel 1) Resolve the issue in the parallel call of IsDirectory by adding the wait of all the scheduled function to finish and revising the data structure to be thread-safe. 2) Resolve a bug in serialization. 3) Refactor the logic to be more straightforward and easy to understand. --- .../kernels/data/matching_files_dataset_op.cc | 107 +++++++++--------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 4afd84f9c7..f3b6769bb8 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -18,6 +18,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/kernels/data/dataset.h" +#include "tensorflow/core/lib/core/blocking_counter.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/threadpool.h" #include "tensorflow/core/lib/io/buffered_inputstream.h" @@ -28,7 +29,6 @@ limitations under the License. #include "tensorflow/core/lib/io/zlib_compression_options.h" #include "tensorflow/core/lib/io/zlib_inputstream.h" #include "tensorflow/core/platform/env.h" -#include "tensorflow/core/lib/core/blocking_counter.h" namespace tensorflow { namespace data { @@ -50,8 +50,6 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { pattern_strs.push_back(patterns(i)); } - // keep the elements in the ascending order - std::sort(pattern_strs.begin(), pattern_strs.end()); *output = new Dataset(ctx, std::move(pattern_strs)); } @@ -109,32 +107,39 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // All the elements in the heap will be the matched filename or the // potential directory. if (!filepath_queue_.empty()) { - string cur_file = filepath_queue_.top(); + string current_file = filepath_queue_.top(); filepath_queue_.pop(); // We can also use isDectory() here. But IsDirectory call can be // expensive for some FS. - if (ctx->env()->MatchPath(cur_file, current_pattern_)) { + if (ctx->env()->MatchPath(current_file, current_pattern_)) { Tensor filepath_tensor(ctx->allocator({}), DT_STRING, {}); - filepath_tensor.scalar()() = cur_file; + filepath_tensor.scalar()() = current_file; out_tensors->emplace_back(std::move(filepath_tensor)); *end_of_sequence = false; return Status::OK(); } - // In this case, cur_file is a directory. Then create a sub-pattern - // to continue the search. - size_t pos = current_pattern_.find_first_of("*?[\\"); - size_t len = current_pattern_.size() - pos; - string cur_pattern_suffix = current_pattern_.substr(pos, len); - string sub_pattern = - strings::StrCat(cur_file, "/", cur_pattern_suffix); - Status s = UpdateIterator(ctx, sub_pattern); + // In this case, current_file is a directory. Then continue the + // search. + const string& current_dir = current_file; + Status s = UpdateIterator(ctx, current_dir, current_pattern_); ret.Update(s); } else { // search a new pattern current_pattern_ = dataset()->patterns_[current_pattern_index_]; - Status s = UpdateIterator(ctx, current_pattern_); + string fixed_prefix = current_pattern_.substr( + 0, current_pattern_.find_first_of("*?[\\")); + string current_dir(io::Dirname(fixed_prefix)); + + // If dir is empty then we need to fix up fixed_prefix and + // current_pattern_ to include . as the top level directory. + if (current_dir.empty()) { + current_dir = "."; + current_pattern_ = io::JoinPath(current_dir, current_pattern_); + } + + Status s = UpdateIterator(ctx, current_dir, current_pattern_); ret.Update(s); ++current_pattern_index_; } @@ -156,13 +161,17 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { if (!filepath_queue_.empty()) { TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("queue_size"), filepath_queue_.size())); - for (int i = 0; i < filepath_queue_.size(); ++i) { + int i = 0; + while (!filepath_queue_.empty()) { TF_RETURN_IF_ERROR(writer->WriteScalar( full_name(strings::StrCat("queue_element_", i)), filepath_queue_.top())); filepath_queue_.pop(); + i++; } } + + return Status::OK(); } Status RestoreInternal(IteratorContext* ctx, @@ -176,32 +185,27 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("current_pattern"), ¤t_pattern_)); - int64 queue_size; - TF_RETURN_IF_ERROR( - reader->ReadScalar(full_name("queue_size"), &queue_size)); - for (int i = 0; i < queue_size; i++) { - string element; - TF_RETURN_IF_ERROR(reader->ReadScalar( - full_name(strings::StrCat("queue_element_", i)), &element)); - filepath_queue_.push(element); + if (reader->Contains(full_name("queue_size"))) { + int64 queue_size; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name("queue_size"), &queue_size)); + for (int i = 0; i < queue_size; i++) { + string element; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat("queue_element_", i)), &element)); + filepath_queue_.push(element); + } } + return Status::OK(); } private: - Status UpdateIterator(IteratorContext* ctx, const string& pattern) + Status UpdateIterator(IteratorContext* ctx, const string& dir, + const string& eval_pattern) EXCLUSIVE_LOCKS_REQUIRED(mu_) { - string fixed_prefix = pattern.substr(0, pattern.find_first_of("*?[\\")); - string eval_pattern = pattern; - string dir(io::Dirname(fixed_prefix)); - - // If dir is empty then we need to fix up fixed_prefix and eval_pattern - // to include . as the top level directory. - if (dir.empty()) { - dir = "."; - fixed_prefix = io::JoinPath(dir, fixed_prefix); - eval_pattern = io::JoinPath(dir, pattern); - } + string fixed_prefix = + eval_pattern.substr(0, eval_pattern.find_first_of("*?[\\")); FileSystem* fs; TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile(dir, &fs)); @@ -215,21 +219,21 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // DFS to find the first element in the iterator. while (!filepath_queue_.empty()) { - string cur_dir = filepath_queue_.top(); + string current_dir = filepath_queue_.top(); filepath_queue_.pop(); std::vector children; - Status s = fs->GetChildren(cur_dir, &children); + Status s = fs->GetChildren(current_dir, &children); ret.Update(s); - // If cur_dir has no children, there will two possible situations: 1) - // the cur_dir is an empty dir; 2) the cur_dir is actual a file - // instead of a director. For the first one, continue to search the - // heap. For the second one, if the file matches the pattern, add + // If current_dir has no children, there will two possible situations: + // 1) the current_dir is an empty dir; 2) the current_dir is actual a + // file instead of a director. For the first one, continue to search + // the heap. For the second one, if the file matches the pattern, add // it to the heap and finish the search; otherwise, continue the next // search. if (children.empty()) { - if (ctx->env()->MatchPath(cur_dir, eval_pattern)) { - filepath_queue_.push(cur_dir); + if (ctx->env()->MatchPath(current_dir, eval_pattern)) { + filepath_queue_.push(current_dir); return ret; } else { continue; @@ -241,14 +245,14 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // This IsDirectory call can be expensive for some FS. Parallelizing // it. - auto is_directory_fn = [fs, &cur_dir, &children, &fixed_prefix, - &children_dir_status](int i) { - const string child_path = io::JoinPath(cur_dir, children[i]); + auto is_directory_fn = [fs, ¤t_dir, &children, &fixed_prefix, + &children_dir_status](int i) { + const string child_path = io::JoinPath(current_dir, children[i]); // In case the child_path doesn't start with the fixed_prefix, then // we don't need to explore this path. if (!str_util::StartsWith(child_path, fixed_prefix)) { - children_dir_status[i] = Status( - tensorflow::error::CANCELLED, "Operation not needed"); + children_dir_status[i] = + Status(tensorflow::error::CANCELLED, "Operation not needed"); } else { children_dir_status[i] = fs->IsDirectory(child_path); } @@ -264,7 +268,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { counter.Wait(); for (int i = 0; i < children.size(); i++) { - const string child_dir_path = io::JoinPath(cur_dir, children[i]); + const string child_dir_path = + io::JoinPath(current_dir, children[i]); const Status child_dir_status = children_dir_status[i]; // If the IsDirectory call was cancelled we bail. if (child_dir_status.code() == tensorflow::error::CANCELLED) { @@ -287,7 +292,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } mutex mu_; - std::priority_queue, std::less> + std::priority_queue, std::greater> filepath_queue_ GUARDED_BY(mu_); size_t current_pattern_index_ GUARDED_BY(mu_) = 0; string current_pattern_ GUARDED_BY(mu_); -- GitLab From 68a4cee2d99672d6e7f45a2d9659f8cab54a2d42 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Sun, 30 Sep 2018 13:33:07 -0700 Subject: [PATCH 0068/1825] Adjust the size of nested directories to make it finish in 60s for different hardwares --- .../python/data/kernel_tests/matching_files_dataset_op_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index a000b9b816..7c72b8043f 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -156,7 +156,7 @@ class MatchingFilesDatasetBenchmark(test.Benchmark): def benchmarkNestedDirectories(self): tmp_dir = tempfile.mkdtemp() - width = 1000 + width = 500 depth = 10 for i in range(width): for j in range(depth): -- GitLab From 4cf6f3c379fd3f0c5c8edc08ff216022a4749c8f Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 1 Oct 2018 22:55:01 -0700 Subject: [PATCH 0069/1825] Fix the coding style issue in tensorflow/core/ops/dataset_ops.cc --- tensorflow/core/ops/dataset_ops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/ops/dataset_ops.cc b/tensorflow/core/ops/dataset_ops.cc index 14596e7f4e..38a97ae653 100644 --- a/tensorflow/core/ops/dataset_ops.cc +++ b/tensorflow/core/ops/dataset_ops.cc @@ -623,7 +623,7 @@ REGISTER_OP("MatchingFilesDataset") .Input("patterns: string") .Output("handle: variant") .SetIsStateful() // TODO(b/65524810): Source dataset ops must be marked - // stateful to inhibit constant folding. + // stateful to inhibit constant folding. .SetShapeFn([](shape_inference::InferenceContext* c) { shape_inference::ShapeHandle unused; // `patterns` must be a scalar or a vector. -- GitLab From e851cdcc7e95d501b07af35dd40e4f938c7aa38c Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 1 Oct 2018 23:11:13 -0700 Subject: [PATCH 0070/1825] Add dependencies for matching_files_dataset_serialization_test.py --- .../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 e8101c038a..a5bfd3afd5 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD @@ -339,6 +339,7 @@ py_test( srcs_version = "PY2AND3", deps = [ ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", "//tensorflow/python/data/ops:dataset_ops", "//third_party/py/numpy", ], -- GitLab From 847ddaedf2cdd59c2d6bf49576466ef440a903f7 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Tue, 2 Oct 2018 00:07:44 -0700 Subject: [PATCH 0071/1825] Move matching_files_dataset_serialization_test.py to tf.data.experimental --- .../serialization/matching_files_dataset_serialization_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tensorflow/{contrib/data/python => python/data/experimental}/kernel_tests/serialization/matching_files_dataset_serialization_test.py (95%) diff --git a/tensorflow/contrib/data/python/kernel_tests/serialization/matching_files_dataset_serialization_test.py b/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py similarity index 95% rename from tensorflow/contrib/data/python/kernel_tests/serialization/matching_files_dataset_serialization_test.py rename to tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py index 41926f7ae1..d2e6f92726 100644 --- a/tensorflow/contrib/data/python/kernel_tests/serialization/matching_files_dataset_serialization_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py @@ -21,7 +21,7 @@ import os import shutil import tempfile -from tensorflow.contrib.data.python.kernel_tests.serialization import dataset_serialization_test_base +from tensorflow.python.data.experimental.kernel_tests.serialization import dataset_serialization_test_base from tensorflow.python.data.ops.dataset_ops import MatchingFilesDataset from tensorflow.python.platform import test -- GitLab From c45d576f8d332c386507fab0ecc443624e1e5f5b Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Tue, 2 Oct 2018 09:49:19 -0700 Subject: [PATCH 0072/1825] Add the implementation of _inputs() function --- tensorflow/python/data/ops/dataset_ops.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 05a7f5d41c..8f3190d3bb 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -2663,6 +2663,9 @@ class MatchingFilesDataset(Dataset): def _as_variant_tensor(self): return gen_dataset_ops.matching_files_dataset(self._patterns) + def _inputs(self): + return [] + @property def output_classes(self): return ops.Tensor -- GitLab From c69e84b5fb1ae7d0d272c37a59a6c94839211af1 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Tue, 2 Oct 2018 09:59:45 -0700 Subject: [PATCH 0073/1825] Make the os.makedirs compatable with Python2 --- .../python/data/kernel_tests/matching_files_dataset_op_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index 7c72b8043f..6e5d845922 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -125,7 +125,7 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): for j in range(depth): new_base = os.path.join(self.tmp_dir, str(i), *[str(dir_name) for dir_name in range(j)]) - os.makedirs(new_base, exist_ok=True) + os.makedirs(new_base) for f in ['a.txt', 'b.py', 'c.pyc']: filename = os.path.join(new_base, f) filenames.append(filename) -- GitLab From ce2e925493cead88de6546ad2754d953694d91c3 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Tue, 2 Oct 2018 11:26:45 -0700 Subject: [PATCH 0074/1825] Rename the variable name from to to avoid the built-in --- .../data/kernel_tests/matching_files_dataset_op_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index 6e5d845922..ac8beaf9e7 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -140,8 +140,8 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): dataset = MatchingFilesDataset(patterns) with self.cached_session() as sess: next_element = dataset.make_one_shot_iterator().get_next() - expected_filenames = [compat.as_bytes(file) - for file in filenames if file.endswith('.txt')] + expected_filenames = [compat.as_bytes(filename) + for filename in filenames if filename.endswith('.txt')] actual_filenames = [] while True: try: -- GitLab From 1a35352d7368adefdca0cf8eaa58ea589d4c48c8 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Tue, 2 Oct 2018 12:21:17 -0700 Subject: [PATCH 0075/1825] short the line to be inside 80 characters --- .../python/data/kernel_tests/matching_files_dataset_op_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index ac8beaf9e7..cd2bb118fb 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -141,7 +141,8 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): with self.cached_session() as sess: next_element = dataset.make_one_shot_iterator().get_next() expected_filenames = [compat.as_bytes(filename) - for filename in filenames if filename.endswith('.txt')] + for filename in filenames + if filename.endswith('.txt')] actual_filenames = [] while True: try: -- GitLab From 3dcb7ebf144fce8d8825e2f12f3707c3e5ca5995 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 4 Oct 2018 23:29:00 -0700 Subject: [PATCH 0076/1825] Address the comments from the internal review --- .../kernels/data/matching_files_dataset_op.cc | 110 ++++++++++-------- ...tching_files_dataset_serialization_test.py | 6 +- .../matching_files_dataset_op_test.py | 27 +++-- 3 files changed, 83 insertions(+), 60 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index f3b6769bb8..f11dd238df 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -46,7 +46,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::vector pattern_strs; pattern_strs.reserve(num_patterns); - for (int i = 0; i < num_patterns; i++) { + for (size_t i = 0; i < num_patterns; i++) { pattern_strs.push_back(patterns(i)); } @@ -100,39 +100,39 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::vector* out_tensors, bool* end_of_sequence) override { mutex_lock l(mu_); - Status ret; + Status ret; // Status to return while (!filepath_queue_.empty() || current_pattern_index_ < dataset()->patterns_.size()) { - // All the elements in the heap will be the matched filename or the - // potential directory. + // All the elements in the heap will be the matched filenames or the + // potential directories. if (!filepath_queue_.empty()) { - string current_file = filepath_queue_.top(); + const PathStatus current_path = filepath_queue_.top(); filepath_queue_.pop(); - // We can also use isDectory() here. But IsDirectory call can be - // expensive for some FS. - if (ctx->env()->MatchPath(current_file, current_pattern_)) { + if (!current_path.second) { Tensor filepath_tensor(ctx->allocator({}), DT_STRING, {}); - filepath_tensor.scalar()() = current_file; + filepath_tensor.scalar()() = + std::move(current_path.first); out_tensors->emplace_back(std::move(filepath_tensor)); *end_of_sequence = false; return Status::OK(); } - // In this case, current_file is a directory. Then continue the + // In this case, current_path is a directory. Then continue the // search. - const string& current_dir = current_file; - Status s = UpdateIterator(ctx, current_dir, current_pattern_); + Status s = + UpdateIterator(ctx, current_path.first, current_pattern_); ret.Update(s); } else { // search a new pattern current_pattern_ = dataset()->patterns_[current_pattern_index_]; - string fixed_prefix = current_pattern_.substr( - 0, current_pattern_.find_first_of("*?[\\")); + StringPiece fixed_prefix = + StringPiece(current_pattern_) + .substr(0, current_pattern_.find_first_of("*?[\\")); string current_dir(io::Dirname(fixed_prefix)); - // If dir is empty then we need to fix up fixed_prefix and + // If current_dir is empty then we need to fix up fixed_prefix and // current_pattern_ to include . as the top level directory. if (current_dir.empty()) { current_dir = "."; @@ -146,7 +146,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } *end_of_sequence = true; - return Status::OK(); + return ret; } protected: @@ -163,9 +163,12 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { filepath_queue_.size())); int i = 0; while (!filepath_queue_.empty()) { + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name(strings::StrCat("path_", i)), + filepath_queue_.top().first)); TF_RETURN_IF_ERROR(writer->WriteScalar( - full_name(strings::StrCat("queue_element_", i)), - filepath_queue_.top())); + full_name(strings::StrCat("path_status_", i)), + filepath_queue_.top().second)); filepath_queue_.pop(); i++; } @@ -190,10 +193,13 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { TF_RETURN_IF_ERROR( reader->ReadScalar(full_name("queue_size"), &queue_size)); for (int i = 0; i < queue_size; i++) { - string element; + string path; + int64 path_status; TF_RETURN_IF_ERROR(reader->ReadScalar( - full_name(strings::StrCat("queue_element_", i)), &element)); - filepath_queue_.push(element); + full_name(strings::StrCat("path_", i)), &path)); + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat("path_status_", i)), &path_status)); + filepath_queue_.push(PathStatus(path, path_status)); } } @@ -204,55 +210,56 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { Status UpdateIterator(IteratorContext* ctx, const string& dir, const string& eval_pattern) EXCLUSIVE_LOCKS_REQUIRED(mu_) { - string fixed_prefix = - eval_pattern.substr(0, eval_pattern.find_first_of("*?[\\")); + StringPiece fixed_prefix = + StringPiece(eval_pattern) + .substr(0, eval_pattern.find_first_of("*?[\\")); FileSystem* fs; TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile(dir, &fs)); - filepath_queue_.push(dir); + filepath_queue_.push(PathStatus(dir, true)); Status ret; // Status to return - // children_dir_status holds is_dir status for children. It can have - // three possible values: OK for true; FAILED_PRECONDITION for false; - // CANCELLED if we don't calculate IsDirectory (we might do that because - // there isn't any point in exploring that child path). // DFS to find the first element in the iterator. while (!filepath_queue_.empty()) { - string current_dir = filepath_queue_.top(); + const PathStatus current_path = filepath_queue_.top(); + + // All the files in the heap are matched with the pattern, so finish + // the search if current_path is a file. + if (!current_path.second) { + return Status::OK(); + } + filepath_queue_.pop(); + + // If current_path is a directory, search its children. + const string& current_dir = current_path.first; std::vector children; Status s = fs->GetChildren(current_dir, &children); ret.Update(s); - // If current_dir has no children, there will two possible situations: - // 1) the current_dir is an empty dir; 2) the current_dir is actual a - // file instead of a director. For the first one, continue to search - // the heap. For the second one, if the file matches the pattern, add - // it to the heap and finish the search; otherwise, continue the next - // search. - if (children.empty()) { - if (ctx->env()->MatchPath(current_dir, eval_pattern)) { - filepath_queue_.push(current_dir); - return ret; - } else { - continue; - } + // If GetChildren() fails, continue the next search. + if (!s.ok()) { + continue; } + // children_dir_status holds is_dir status for children. It can have + // three possible values: OK for true; FAILED_PRECONDITION for false; + // CANCELLED if we don't calculate IsDirectory (we might do that + // because there isn't any point in exploring that child path). std::vector children_dir_status; children_dir_status.resize(children.size()); // This IsDirectory call can be expensive for some FS. Parallelizing // it. - auto is_directory_fn = [fs, ¤t_dir, &children, &fixed_prefix, + auto is_directory_fn = [fs, current_dir, &children, &fixed_prefix, &children_dir_status](int i) { const string child_path = io::JoinPath(current_dir, children[i]); // In case the child_path doesn't start with the fixed_prefix, then // we don't need to explore this path. if (!str_util::StartsWith(child_path, fixed_prefix)) { children_dir_status[i] = - Status(tensorflow::error::CANCELLED, "Operation not needed"); + errors::Cancelled("Operation not needed"); } else { children_dir_status[i] = fs->IsDirectory(child_path); } @@ -268,22 +275,24 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { counter.Wait(); for (int i = 0; i < children.size(); i++) { - const string child_dir_path = + const string& child_dir_path = io::JoinPath(current_dir, children[i]); - const Status child_dir_status = children_dir_status[i]; + const Status& child_dir_status = children_dir_status[i]; + // If the IsDirectory call was cancelled we bail. if (child_dir_status.code() == tensorflow::error::CANCELLED) { + ret.Update(child_dir_status); continue; } if (child_dir_status.ok()) { // push the child dir for next search - filepath_queue_.push(child_dir_path); + filepath_queue_.push(PathStatus(child_dir_path, true)); } else { // This case will be a file: if the file matches the pattern, push // it to the heap; otherwise, ignore it. if (ctx->env()->MatchPath(child_dir_path, eval_pattern)) { - filepath_queue_.push(child_dir_path); + filepath_queue_.push(PathStatus(child_dir_path, false)); } } } @@ -292,7 +301,10 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } mutex mu_; - std::priority_queue, std::greater> + // True means the path is a directory; False means the path is a filename. + typedef std::pair PathStatus; + std::priority_queue, + std::greater> filepath_queue_ GUARDED_BY(mu_); size_t current_pattern_index_ GUARDED_BY(mu_) = 0; string current_pattern_ GUARDED_BY(mu_); diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py b/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py index d2e6f92726..41146c9786 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py @@ -48,9 +48,9 @@ class MatchingFilesDatasetSerializationTest( patterns = [] for i in range(depth): - pattern = '{}/{}/*.txt'.format(tmp_dir, - os.path.join( - *['**' for _ in range(i + 1)])) + pattern = os.path.join(tmp_dir, + os.path.join(*['**' for _ in range(i + 1)]), + '*.txt') patterns.append(pattern) num_outputs = width * depth diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index cd2bb118fb..4b59500bd4 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -45,6 +45,16 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): for filename in filenames: open(os.path.join(self.tmp_dir, filename), 'a').close() + def testNonExistingDirectory(self): + """Test the MatchingFiles dataset with a non-existing directory""" + + self.tearDown() + dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) + with self.cached_session() as sess: + next_element = dataset.make_one_shot_iterator().get_next() + with self.assertRaises(errors.NotFoundError): + sess.run(next_element) + def testEmptyDirectory(self): """Test the MatchingFiles dataset with an empty directory""" @@ -98,15 +108,15 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): def testFileMiddles(self): """Test the MatchingFiles dataset using the middles of filename""" - filenames = ['a.txt', 'b.py', 'c.pyc'] + filenames = ['aa.txt', 'bb.py', 'bbc.pyc', 'cc.pyc'] self._touchTempFiles(filenames) - dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*.py*')) + dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, 'b*.py*')) with self.cached_session() as sess: next_element = dataset.make_one_shot_iterator().get_next() expected_filenames = [] actual_filenames = [] - for filename in filenames[1:]: + for filename in filenames[1:3]: expected_filenames.append( compat.as_bytes(os.path.join(self.tmp_dir, filename))) actual_filenames.append(compat.as_bytes(sess.run(next_element))) @@ -133,8 +143,8 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): patterns = [] for i in range(depth): - pattern = '{}/{}/*.txt'.format( - self.tmp_dir, os.path.join(*['**' for _ in range(i + 1)])) + pattern = os.path.join( + self.tmp_dir, os.path.join(*['**' for _ in range(i + 1)]), '*.txt') patterns.append(pattern) dataset = MatchingFilesDataset(patterns) @@ -171,9 +181,10 @@ class MatchingFilesDatasetBenchmark(test.Benchmark): patterns = [] for i in range(depth): - pattern = '{}/{}/*.txt'.format(tmp_dir, - os.path.join( - *['**' for _ in range(i + 1)])) + pattern = os.path.join(tmp_dir, + os.path.join(*['**' for _ in range(i + 1)]), + '*.txt') + patterns.append(pattern) deltas = [] -- GitLab From efcefef05d317e94b107bf3c56906c653c217a31 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 5 Oct 2018 00:46:44 -0700 Subject: [PATCH 0077/1825] Bail the error --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index f11dd238df..7dd40c9e44 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -281,7 +281,6 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // If the IsDirectory call was cancelled we bail. if (child_dir_status.code() == tensorflow::error::CANCELLED) { - ret.Update(child_dir_status); continue; } -- GitLab From a79bf6cd55588d9122e7ec4fdf0ff9e1d50a43d6 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 5 Oct 2018 14:46:02 -0700 Subject: [PATCH 0078/1825] Minor change on coding style --- .../core/kernels/data/matching_files_dataset_op.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 7dd40c9e44..e2aa45b38a 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -100,7 +100,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::vector* out_tensors, bool* end_of_sequence) override { mutex_lock l(mu_); - Status ret; // Status to return + Status ret; while (!filepath_queue_.empty() || current_pattern_index_ < dataset()->patterns_.size()) { @@ -121,9 +121,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // In this case, current_path is a directory. Then continue the // search. - Status s = - UpdateIterator(ctx, current_path.first, current_pattern_); - ret.Update(s); + ret.Update( + UpdateIterator(ctx, current_path.first, current_pattern_)); } else { // search a new pattern current_pattern_ = dataset()->patterns_[current_pattern_index_]; @@ -139,8 +138,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { current_pattern_ = io::JoinPath(current_dir, current_pattern_); } - Status s = UpdateIterator(ctx, current_dir, current_pattern_); - ret.Update(s); + ret.Update(UpdateIterator(ctx, current_dir, current_pattern_)); ++current_pattern_index_; } } -- GitLab From 6605eb19bd1ee64d7e58ca982ee560346809e2be Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 8 Oct 2018 21:17:20 -0700 Subject: [PATCH 0079/1825] Add the temporary logs --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index e2aa45b38a..2abac92e5d 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -137,6 +137,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { current_dir = "."; current_pattern_ = io::JoinPath(current_dir, current_pattern_); } + std::cout << "Input pattern: " << current_pattern_ + << "; Current dir: " << current_dir << std::endl; ret.Update(UpdateIterator(ctx, current_dir, current_pattern_)); ++current_pattern_index_; @@ -213,6 +215,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { .substr(0, eval_pattern.find_first_of("*?[\\")); FileSystem* fs; + Status fs_status = ctx->env()->GetFileSystemForFile(dir, &fs); + std::cout << "GetFileSystemForFile status: " << fs_status << std::endl; TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile(dir, &fs)); filepath_queue_.push(PathStatus(dir, true)); @@ -234,6 +238,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { const string& current_dir = current_path.first; std::vector children; Status s = fs->GetChildren(current_dir, &children); + std::cout << "GetChildren status: " << s.ToString() + << "; Children size: " << children.size() << std::endl; ret.Update(s); // If GetChildren() fails, continue the next search. -- GitLab From 597f04e949285f7e72682c7c3a6ed656a5aedb1e Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Tue, 9 Oct 2018 00:38:47 -0700 Subject: [PATCH 0080/1825] Add mroe logging infor --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 2abac92e5d..74494a302c 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -239,7 +239,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::vector children; Status s = fs->GetChildren(current_dir, &children); std::cout << "GetChildren status: " << s.ToString() - << "; Children size: " << children.size() << std::endl; + << "; Children size: " << children.size() + << "; Heap size: " << filepath_queue_.size() << std::endl; ret.Update(s); // If GetChildren() fails, continue the next search. @@ -282,6 +283,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { const string& child_dir_path = io::JoinPath(current_dir, children[i]); const Status& child_dir_status = children_dir_status[i]; + std::cout << "Child dir path: " << child_dir_path << std::endl; // If the IsDirectory call was cancelled we bail. if (child_dir_status.code() == tensorflow::error::CANCELLED) { -- GitLab From 775188c321335b1053fd1fb174efd607e5173d59 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 9 Oct 2018 20:51:30 +0000 Subject: [PATCH 0081/1825] Add validation to axis for tf.nn.softmax This fix tries to address the issue raised in 22793 where an invalid axis (outside of `[-dim, dim)`) still returns value. This behavior is different from most other ops in tf like `tf.argmax`/etc. This fix add the validation of axis so that an error will be returned in case of invalid axis. This fix fixes 22793. Signed-off-by: Yong Tang --- tensorflow/python/ops/nn_ops.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index 04962da7f7..56f36260f9 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -30,6 +30,7 @@ from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_util from tensorflow.python.ops import array_ops +from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import check_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import math_ops @@ -1679,22 +1680,23 @@ def _softmax(logits, compute_op, dim=-1, name=None): # If dim is not the last dimension, we have to do a transpose so that we can # still perform softmax on its last dimension. + is_valid_dim = control_flow_ops.Assert(math_ops.logical_and(math_ops.greater_equal(dim, -shape.ndims), math_ops.less(dim, shape.ndims)), [dim]) + with ops.control_dependencies([is_valid_dim]): + # Swap logits' dimension of dim and its last dimension. + input_rank = array_ops.rank(logits) + dim_axis = dim % shape.ndims + logits = _swap_axis(logits, dim_axis, math_ops.subtract(input_rank, 1)) - # Swap logits' dimension of dim and its last dimension. - input_rank = array_ops.rank(logits) - dim_axis = dim % shape.ndims - logits = _swap_axis(logits, dim_axis, math_ops.subtract(input_rank, 1)) + # Do the actual softmax on its last dimension. + output = compute_op(logits) - # Do the actual softmax on its last dimension. - output = compute_op(logits) + output = _swap_axis( + output, dim_axis, math_ops.subtract(input_rank, 1), name=name) - output = _swap_axis( - output, dim_axis, math_ops.subtract(input_rank, 1), name=name) + # Make shape inference work since transpose may erase its static shape. + output.set_shape(shape) - # Make shape inference work since transpose may erase its static shape. - output.set_shape(shape) - - return output + return output @tf_export("nn.softmax", "math.softmax") -- GitLab From 7ec309774d2eeb4285a0eb6ba0585848fc50054b Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 9 Oct 2018 20:56:17 +0000 Subject: [PATCH 0082/1825] Pylint fix Signed-off-by: Yong Tang --- tensorflow/python/ops/nn_ops.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index 56f36260f9..70601dfaba 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -30,8 +30,8 @@ from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_util from tensorflow.python.ops import array_ops -from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import check_ops +from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import random_ops @@ -1680,7 +1680,9 @@ def _softmax(logits, compute_op, dim=-1, name=None): # If dim is not the last dimension, we have to do a transpose so that we can # still perform softmax on its last dimension. - is_valid_dim = control_flow_ops.Assert(math_ops.logical_and(math_ops.greater_equal(dim, -shape.ndims), math_ops.less(dim, shape.ndims)), [dim]) + is_valid_dim = control_flow_ops.Assert(math_ops.logical_and( + math_ops.greater_equal(dim, -shape.ndims), + math_ops.less(dim, shape.ndims)), [dim]) with ops.control_dependencies([is_valid_dim]): # Swap logits' dimension of dim and its last dimension. input_rank = array_ops.rank(logits) -- GitLab From 980227aabdd20da19a8824d1f828e22fb8bf5c1e Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 9 Oct 2018 20:56:55 +0000 Subject: [PATCH 0083/1825] Add test case for axis validation with tf.nn.softmax Signed-off-by: Yong Tang --- tensorflow/python/kernel_tests/softmax_op_test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tensorflow/python/kernel_tests/softmax_op_test.py b/tensorflow/python/kernel_tests/softmax_op_test.py index 89f4697e5c..55849304e8 100644 --- a/tensorflow/python/kernel_tests/softmax_op_test.py +++ b/tensorflow/python/kernel_tests/softmax_op_test.py @@ -222,6 +222,13 @@ class SoftmaxTest(test.TestCase): with self.assertRaises(errors_impl.InvalidArgumentError): nn_ops.softmax([1., 2., 3., 4.], axis=dim).eval() + def testInvalidAxis(self): + # Test case for GitHub issue 22793. + with self.cached_session(): + ones = array_ops.ones(shape=[2, 3]) + with self.assertRaises(errors_impl.InvalidArgumentError): + nn_ops.softmax(ones, axis=2).eval() + def testLargeDims(self): # Make sure that we properly handle large inputs. See # https://github.com/tensorflow/tensorflow/issues/4425 for details -- GitLab From ff1b492e75877c684b81335495b46b9503e89172 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Tue, 9 Oct 2018 17:40:24 -0700 Subject: [PATCH 0084/1825] Temporary code for fixing the Windows backslash issue --- .../kernels/data/matching_files_dataset_op.cc | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 74494a302c..0158b31d92 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -101,17 +101,29 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { bool* end_of_sequence) override { mutex_lock l(mu_); Status ret; + FileSystem* fs; while (!filepath_queue_.empty() || current_pattern_index_ < dataset()->patterns_.size()) { // All the elements in the heap will be the matched filenames or the // potential directories. if (!filepath_queue_.empty()) { - const PathStatus current_path = filepath_queue_.top(); + PathStatus current_path = filepath_queue_.top(); filepath_queue_.pop(); + TF_RETURN_IF_ERROR( + ctx->env()->GetFileSystemForFile(current_path.first, &fs)); + if (!current_path.second) { Tensor filepath_tensor(ctx->allocator({}), DT_STRING, {}); + + // Replace the forward slash by the backslash for Windows path + if (dataset()->patterns_[current_pattern_index_ - 1].find('\\') != + std::string::npos) { + std::replace(current_path.first.begin(), + current_path.first.end(), '/', '\\'); + } + filepath_tensor.scalar()() = std::move(current_path.first); out_tensors->emplace_back(std::move(filepath_tensor)); @@ -122,10 +134,24 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // In this case, current_path is a directory. Then continue the // search. ret.Update( - UpdateIterator(ctx, current_path.first, current_pattern_)); + UpdateIterator(ctx, fs, current_path.first, current_pattern_)); } else { // search a new pattern current_pattern_ = dataset()->patterns_[current_pattern_index_]; + TF_RETURN_IF_ERROR( + ctx->env()->GetFileSystemForFile(current_pattern_, &fs)); + + // Windows paths contain backslashes and Windows APIs accept forward + // and backslashes equivalently, so we convert the pattern to use + // forward slashes exclusively. The backslash is used as the + // indicator of Windows paths. Note that this is not ideal, since + // the API expects backslash as an escape character, but no code + // appears to rely on this behavior + if (current_pattern_.find('\\') != std::string::npos) { + std::replace(current_pattern_.begin(), current_pattern_.end(), + '\\', '/'); + } + StringPiece fixed_prefix = StringPiece(current_pattern_) .substr(0, current_pattern_.find_first_of("*?[\\")); @@ -140,7 +166,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::cout << "Input pattern: " << current_pattern_ << "; Current dir: " << current_dir << std::endl; - ret.Update(UpdateIterator(ctx, current_dir, current_pattern_)); + ret.Update(UpdateIterator(ctx, fs, current_dir, current_pattern_)); ++current_pattern_index_; } } @@ -207,18 +233,13 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } private: - Status UpdateIterator(IteratorContext* ctx, const string& dir, - const string& eval_pattern) + Status UpdateIterator(IteratorContext* ctx, FileSystem* fs, + const string& dir, const string& eval_pattern) EXCLUSIVE_LOCKS_REQUIRED(mu_) { StringPiece fixed_prefix = StringPiece(eval_pattern) .substr(0, eval_pattern.find_first_of("*?[\\")); - FileSystem* fs; - Status fs_status = ctx->env()->GetFileSystemForFile(dir, &fs); - std::cout << "GetFileSystemForFile status: " << fs_status << std::endl; - TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile(dir, &fs)); - filepath_queue_.push(PathStatus(dir, true)); Status ret; // Status to return -- GitLab From 7efb78e55c9993068fdc82df3d2df9d989d111e4 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 10 Oct 2018 14:22:32 +0000 Subject: [PATCH 0085/1825] Add test case for tf.keras.regularizers.{l1,l2}(0.) with tf.get_variable Signed-off-by: Yong Tang --- tensorflow/python/keras/integration_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tensorflow/python/keras/integration_test.py b/tensorflow/python/keras/integration_test.py index 3c0f73b1c3..0e6fc79dd3 100644 --- a/tensorflow/python/keras/integration_test.py +++ b/tensorflow/python/keras/integration_test.py @@ -26,6 +26,7 @@ from tensorflow.python.keras import testing_utils from tensorflow.python.layers import core as tf_core_layers from tensorflow.python.ops import nn from tensorflow.python.ops import rnn_cell +from tensorflow.python.ops import variable_scope from tensorflow.python.platform import test @@ -312,6 +313,15 @@ class KerasIntegrationTest(test.TestCase): verbose=0) self.assertGreater(history.history['val_acc'][-1], 0.7) + def test_regularizers_with_get_variable(self): + # Test case for GitHub issue 22470. + with self.cached_session(): + v = variable_scope.get_variable( + "v", + shape = [4, 4], + initializer=keras.initializers.glorot_uniform(), + regularizer=keras.regularizers.l2(0.)) + if __name__ == '__main__': test.main() -- GitLab From 0e1bc5100d72dd75e7b148f0cf1d422ac0c6469b Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 10 Oct 2018 13:38:06 -0700 Subject: [PATCH 0086/1825] Revise the NestedDirectories test working for both Windows and Linux --- ...tching_files_dataset_serialization_test.py | 16 +++++----- .../matching_files_dataset_op_test.py | 30 ++++++++----------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py b/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py index 41146c9786..b120c0a626 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/matching_files_dataset_serialization_test.py @@ -42,21 +42,19 @@ class MatchingFilesDatasetSerializationTest( *[str(dir_name) for dir_name in range(j)]) if not os.path.exists(new_base): os.makedirs(new_base) - for f in ['a.txt', 'b.py', 'c.pyc']: + child_files = ['a.py', 'b.pyc'] if j < depth - 1 else ['c.txt', 'd.log'] + for f in child_files: filename = os.path.join(new_base, f) open(filename, 'w').close() - patterns = [] - for i in range(depth): - pattern = os.path.join(tmp_dir, - os.path.join(*['**' for _ in range(i + 1)]), - '*.txt') - patterns.append(pattern) + patterns = [ + os.path.join(tmp_dir, os.path.join(*['**' for _ in range(depth)]), + suffix) for suffix in ['*.txt', '*.log']] - num_outputs = width * depth + num_outputs = width * len(patterns) self.run_core_tests( lambda: self._build_iterator_graph(patterns), - lambda: self._build_iterator_graph(patterns[0:depth // 2]), num_outputs) + lambda: self._build_iterator_graph(patterns[0:1]), num_outputs) shutil.rmtree(tmp_dir, ignore_errors=True) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index 4b59500bd4..d811844cae 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -136,23 +136,23 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): new_base = os.path.join(self.tmp_dir, str(i), *[str(dir_name) for dir_name in range(j)]) os.makedirs(new_base) - for f in ['a.txt', 'b.py', 'c.pyc']: + child_files = ['a.py', 'b.pyc'] if j < depth - 1 else ['c.txt', 'd.log'] + for f in child_files: filename = os.path.join(new_base, f) filenames.append(filename) open(filename, 'w').close() - patterns = [] - for i in range(depth): - pattern = os.path.join( - self.tmp_dir, os.path.join(*['**' for _ in range(i + 1)]), '*.txt') - patterns.append(pattern) + patterns = [ + os.path.join(self.tmp_dir, os.path.join(*['**' for _ in range(depth)]), + suffix) for suffix in ['*.txt', '*.log']] dataset = MatchingFilesDataset(patterns) with self.cached_session() as sess: next_element = dataset.make_one_shot_iterator().get_next() expected_filenames = [compat.as_bytes(filename) for filename in filenames - if filename.endswith('.txt')] + if filename.endswith('.txt') + or filename.endswith('.log')] actual_filenames = [] while True: try: @@ -173,19 +173,15 @@ class MatchingFilesDatasetBenchmark(test.Benchmark): for j in range(depth): new_base = os.path.join(tmp_dir, str(i), *[str(dir_name) for dir_name in range(j)]) - if not os.path.exists(new_base): - os.makedirs(new_base) - for f in ['a.txt', 'b.py', 'c.pyc']: + os.makedirs(new_base) + child_files = ['a.py', 'b.pyc'] if j < depth - 1 else ['c.txt', 'd.log'] + for f in child_files: filename = os.path.join(new_base, f) open(filename, 'w').close() - patterns = [] - for i in range(depth): - pattern = os.path.join(tmp_dir, - os.path.join(*['**' for _ in range(i + 1)]), - '*.txt') - - patterns.append(pattern) + patterns = [ + os.path.join(tmp_dir, os.path.join(*['**' for _ in range(depth)]), + suffix) for suffix in ['*.txt', '*.log']] deltas = [] iters = 3 -- GitLab From ab69b3450ff9469448b0b1c3e365e860d9ba1600 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 10 Oct 2018 14:42:08 -0700 Subject: [PATCH 0087/1825] Add conversion, tests for Reshape and Transpose --- tensorflow/contrib/tensorrt/BUILD | 1 + .../contrib/tensorrt/convert/convert_graph.cc | 2 + .../contrib/tensorrt/convert/convert_nodes.cc | 57 ++++++ .../tensorrt/test/reshape_transpose_test.py | 188 ++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 tensorflow/contrib/tensorrt/test/reshape_transpose_test.py diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index 5c16fcb760..d756857f18 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -455,6 +455,7 @@ cuda_py_tests( "test/multi_connection_neighbor_engine_test.py", "test/neighboring_engine_test.py", "test/rank_two_test.py", + "test/reshape_transpose_test.py", "test/vgg_block_nchw_test.py", "test/vgg_block_test.py", ], diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index 7ad9bf22d3..4d41761fdb 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -115,6 +115,8 @@ bool IsTensorRTCandidate(const tensorflow::Node* node) { "Sqrt", "Abs", "Neg", + "Transpose", + "Reshape", #if NV_TENSORRT_MAJOR > 3 "MatMul", "BatchMatMul", diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 0ce891782e..e2ed372f12 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -1467,6 +1467,59 @@ tensorflow::Status ConvertPlugin(Converter& ctx, return tensorflow::Status::OK(); } +tensorflow::Status ConvertTranspose( + Converter& ctx, const tensorflow::NodeDef& node_def, + const std::vector& inputs, + std::vector* outputs) { + if (inputs.size() != 2 || !inputs.at(0).is_tensor() || + !inputs.at(1).is_weights()) { + return tensorflow::errors::InvalidArgument( + "Input expects tensor and weights, at", node_def.name()); + } + nvinfer1::ITensor* input_tensor = const_cast( + inputs.at(0).tensor()); + + TRT_ShapedWeights weights = inputs.at(1).weights(); + const int* weights_ptr = static_cast(const_cast( + weights.GetValues())); + std::vector perm(weights.count()); + for (int i = 0; i < weights.count(); i++) { + perm[i] = weights_ptr[i]; + } + + nvinfer1::ITensor* output_tensor = ctx.TransposeTensor(input_tensor, perm); + outputs->push_back(TRT_TensorOrWeights(output_tensor)); + return tensorflow::Status::OK(); +} + +tensorflow::Status ConvertReshape( + Converter& ctx, const tensorflow::NodeDef& node_def, + const std::vector& inputs, + std::vector* outputs) { + if (inputs.size() != 2 || !inputs.at(1).is_weights()) { + return tensorflow::errors::InvalidArgument( + "Input expects weights for shape, at", node_def.name()); + } + + TRT_ShapedWeights weights = inputs.at(1).weights(); + const int* weights_ptr = static_cast(const_cast( + weights.GetValues())); + nvinfer1::Dims new_shape; + // Ignore first (batch) dimension because TRT abstracts batch away + new_shape.nbDims = weights.count() - 1; + for (int i = 1; i < weights.count(); i++) { + new_shape.d[i-1] = weights_ptr[i]; + } + + const nvinfer1::ITensor* output_tensor; + TFTRT_RETURN_ERROR_IF_FALSE( + PrepareTensorForShape(ctx, inputs.at(0), new_shape, &output_tensor), + node_def.name()); + outputs->push_back(TRT_TensorOrWeights( + const_cast(output_tensor))); + return tensorflow::Status::OK(); +} + tensorflow::Status ConvertConv2D(Converter& ctx, const tensorflow::NodeDef& node_def, const std::vector& inputs, @@ -2666,6 +2719,10 @@ void Converter::register_op_converters() { op_registry_["Sqrt"] = ConvertUnary; op_registry_["Abs"] = ConvertUnary; op_registry_["Neg"] = ConvertUnary; + + op_registry_["Transpose"] = ConvertTranspose; + op_registry_["Reshape"] = ConvertReshape; + #if NV_TENSORRT_MAJOR == 3 op_registry_["Mean"] = ConvertReducePool; #endif diff --git a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py new file mode 100644 index 0000000000..a81e3c7bc6 --- /dev/null +++ b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py @@ -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. +# ============================================================================== +"""Basic tests for 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.python import trt_convert +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 math_ops +from tensorflow.python.ops import nn +from tensorflow.python.ops import nn_ops +from tensorflow.python.platform import test + + +class SimpleReshapeTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + # TODO(aaroey): test graph with different dtypes. + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + reshape = array_ops.reshape(inp, [-1, 24*24*2]) + print('RESHAPE SHAPE', reshape.get_shape().as_list()) + identity = array_ops.identity(reshape, "identity") + array_ops.identity(identity, 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=[(100, 24*24*2)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["my_trt_op_0"] + +class ReshapeInverseTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + # TODO(aaroey): test graph with different dtypes. + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + reshape = array_ops.reshape(inp, [-1, 24*24*2]) + reshape = array_ops.reshape(reshape, [-1, 24, 24, 2]) + identity = array_ops.identity(reshape, "identity") + array_ops.identity(identity, 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=[(100, 24, 24, 2)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["my_trt_op_0"] + +class ManyReshapeTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + # TODO(aaroey): test graph with different dtypes. + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + reshape = array_ops.reshape(inp, [-1, 24*24, 2]) + reshape = array_ops.reshape(reshape, [-1, 24*2, 24]) + reshape = array_ops.reshape(reshape, [-1, 24, 24*2]) + reshape = array_ops.reshape(reshape, [-1, 6, 4, 24, 2]) + reshape = array_ops.reshape(reshape, [-1, 6, 4, 6, 4, 2]) + reshape = array_ops.reshape(reshape, [-1, 6, 4, 6, 4, 2, 1]) + reshape = array_ops.reshape(reshape, [-1, 24, 24, 2]) + identity = array_ops.identity(reshape, "identity") + array_ops.identity(identity, 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=[(100, 24, 24, 2)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["my_trt_op_0"] + +class SimpleTransposeTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + # TODO(aaroey): test graph with different dtypes. + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + # to NCHW + transpose = array_ops.transpose(inp, [0, 3, 1, 2]) + identity = array_ops.identity(transpose, "identity") + array_ops.identity(identity, 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=[(100, 2, 24, 24)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["my_trt_op_0"] + +class TransposeInverseTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + # TODO(aaroey): test graph with different dtypes. + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + # to NCHW + transpose = array_ops.transpose(inp, [0, 3, 1, 2]) + # back to NHWC + transpose = array_ops.transpose(transpose, [0, 2, 3, 1]) + identity = array_ops.identity(transpose, "identity") + array_ops.identity(identity, 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=[(100, 24, 24, 2)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return ["my_trt_op_0"] + +if __name__ == "__main__": + test.main() -- GitLab From 307ef84f3c53bb35222182afaf40385fae88ec73 Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Wed, 10 Oct 2018 14:44:05 -0700 Subject: [PATCH 0088/1825] Remove leftover todo comments --- tensorflow/contrib/tensorrt/test/reshape_transpose_test.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py index a81e3c7bc6..81dad5e1a4 100644 --- a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py +++ b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py @@ -36,7 +36,6 @@ class SimpleReshapeTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): """Create a graph containing single segment.""" - # TODO(aaroey): test graph with different dtypes. dtype = dtypes.float32 input_name = "input" input_dims = [100, 24, 24, 2] @@ -65,7 +64,6 @@ class ReshapeInverseTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): """Create a graph containing single segment.""" - # TODO(aaroey): test graph with different dtypes. dtype = dtypes.float32 input_name = "input" input_dims = [100, 24, 24, 2] @@ -94,7 +92,6 @@ class ManyReshapeTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): """Create a graph containing single segment.""" - # TODO(aaroey): test graph with different dtypes. dtype = dtypes.float32 input_name = "input" input_dims = [100, 24, 24, 2] @@ -128,7 +125,6 @@ class SimpleTransposeTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): """Create a graph containing single segment.""" - # TODO(aaroey): test graph with different dtypes. dtype = dtypes.float32 input_name = "input" input_dims = [100, 24, 24, 2] @@ -157,7 +153,6 @@ class TransposeInverseTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): """Create a graph containing single segment.""" - # TODO(aaroey): test graph with different dtypes. dtype = dtypes.float32 input_name = "input" input_dims = [100, 24, 24, 2] -- GitLab From e1978dc07690b97b2708a8ccf66b6f1293ce7dff Mon Sep 17 00:00:00 2001 From: mdfaijul Date: Wed, 10 Oct 2018 15:54:47 -0700 Subject: [PATCH 0089/1825] fixed style with clang-format 3.6.0 --- tensorflow/core/ops/nn_ops.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index 199176e93f..ee0c5ce51d 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -704,8 +704,8 @@ REGISTER_OP("LRNGrad") REGISTER_OP("MaxPool") .Attr( - "T: {half, bfloat16, float, double, int32, int64, uint8, int16, int8, " - "uint16, qint8} = DT_FLOAT") + "T: {half, bfloat16, float, double, int32, int64, uint8, int16, int8, " + "uint16, qint8} = DT_FLOAT") .Attr("ksize: list(int) >= 4") .Attr("strides: list(int) >= 4") .Attr(GetPaddingAttrString()) @@ -716,8 +716,8 @@ REGISTER_OP("MaxPool") REGISTER_OP("MaxPoolV2") .Attr( - "T: {half, bfloat16, float, double, int32, int64, uint8, int16, int8, " - "uint16, qint8} = DT_FLOAT") + "T: {half, bfloat16, float, double, int32, int64, uint8, int16, int8, " + "uint16, qint8} = DT_FLOAT") .Attr(GetPaddingAttrString()) .Attr("data_format: {'NHWC', 'NCHW', 'NCHW_VECT_C'} = 'NHWC'") .Input("input: T") @@ -2262,8 +2262,8 @@ REGISTER_OP("_MklInputConversion") .Output("mkl_output_1: uint8") // All datatypes supported by element-wise ops .Attr( - "T: {half, float, double, uint8, int8, uint16, int16, int32, int64, " - "complex64, complex128}") + "T: {half, float, double, uint8, int8, uint16, int16, int32, int64, " + "complex64, complex128}") .Attr(GetConvnetDataFormat2D3DAttrString()) .SetShapeFn(shape_inference::UnknownShape) .Doc(R"doc( -- GitLab From 9b093e3428c9a24b7c23d7136f45d925eec13258 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 10 Oct 2018 16:45:11 -0700 Subject: [PATCH 0090/1825] Better handle the non-ok status and clean code --- .../kernels/data/matching_files_dataset_op.cc | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 0158b31d92..23587a2d00 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -100,9 +100,14 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::vector* out_tensors, bool* end_of_sequence) override { mutex_lock l(mu_); - Status ret; FileSystem* fs; + TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile( + dataset()->patterns_[(current_pattern_index_ > 0) + ? current_pattern_index_ - 1 + : 0], + &fs)); + while (!filepath_queue_.empty() || current_pattern_index_ < dataset()->patterns_.size()) { // All the elements in the heap will be the matched filenames or the @@ -111,13 +116,10 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { PathStatus current_path = filepath_queue_.top(); filepath_queue_.pop(); - TF_RETURN_IF_ERROR( - ctx->env()->GetFileSystemForFile(current_path.first, &fs)); - if (!current_path.second) { Tensor filepath_tensor(ctx->allocator({}), DT_STRING, {}); - // Replace the forward slash by the backslash for Windows path + // Replace the forward slash with the backslash for Windows path if (dataset()->patterns_[current_pattern_index_ - 1].find('\\') != std::string::npos) { std::replace(current_path.first.begin(), @@ -133,13 +135,11 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // In this case, current_path is a directory. Then continue the // search. - ret.Update( + TF_RETURN_IF_ERROR( UpdateIterator(ctx, fs, current_path.first, current_pattern_)); } else { // search a new pattern current_pattern_ = dataset()->patterns_[current_pattern_index_]; - TF_RETURN_IF_ERROR( - ctx->env()->GetFileSystemForFile(current_pattern_, &fs)); // Windows paths contain backslashes and Windows APIs accept forward // and backslashes equivalently, so we convert the pattern to use @@ -163,16 +163,15 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { current_dir = "."; current_pattern_ = io::JoinPath(current_dir, current_pattern_); } - std::cout << "Input pattern: " << current_pattern_ - << "; Current dir: " << current_dir << std::endl; - ret.Update(UpdateIterator(ctx, fs, current_dir, current_pattern_)); + TF_RETURN_IF_ERROR( + UpdateIterator(ctx, fs, current_dir, current_pattern_)); ++current_pattern_index_; } } *end_of_sequence = true; - return ret; + return Status::OK(); } protected: @@ -259,14 +258,13 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { const string& current_dir = current_path.first; std::vector children; Status s = fs->GetChildren(current_dir, &children); - std::cout << "GetChildren status: " << s.ToString() - << "; Children size: " << children.size() - << "; Heap size: " << filepath_queue_.size() << std::endl; ret.Update(s); // If GetChildren() fails, continue the next search. - if (!s.ok()) { + if (ret.code() == error::NOT_FOUND) { continue; + } else if (!ret.ok()) { + return ret; } // children_dir_status holds is_dir status for children. It can have @@ -304,7 +302,6 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { const string& child_dir_path = io::JoinPath(current_dir, children[i]); const Status& child_dir_status = children_dir_status[i]; - std::cout << "Child dir path: " << child_dir_path << std::endl; // If the IsDirectory call was cancelled we bail. if (child_dir_status.code() == tensorflow::error::CANCELLED) { -- GitLab From f5b79a0a545c74bfef15e86fa8c3fa019b4da7b8 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 10 Oct 2018 17:04:17 -0700 Subject: [PATCH 0091/1825] Minor change on coding style --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 23587a2d00..6ee0bcf6f5 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -103,9 +103,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { FileSystem* fs; TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile( - dataset()->patterns_[(current_pattern_index_ > 0) - ? current_pattern_index_ - 1 - : 0], + dataset() + ->patterns_[std::max(size_t(0), current_pattern_index_ - 1)], &fs)); while (!filepath_queue_.empty() || -- GitLab From 579155d915bd1fe2cfcff9927ca9af996aca1b72 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Wed, 10 Oct 2018 21:29:36 -0700 Subject: [PATCH 0092/1825] Solve the size_t issue in computing pattern index --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 6ee0bcf6f5..23587a2d00 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -103,8 +103,9 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { FileSystem* fs; TF_RETURN_IF_ERROR(ctx->env()->GetFileSystemForFile( - dataset() - ->patterns_[std::max(size_t(0), current_pattern_index_ - 1)], + dataset()->patterns_[(current_pattern_index_ > 0) + ? current_pattern_index_ - 1 + : 0], &fs)); while (!filepath_queue_.empty() || -- GitLab From 879a5020f0b05026951d463bad47c00d94da6879 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 11 Oct 2018 14:06:31 -0700 Subject: [PATCH 0093/1825] Add the logging for the Google internal test --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 23587a2d00..f12e376ddc 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -258,6 +258,9 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { const string& current_dir = current_path.first; std::vector children; Status s = fs->GetChildren(current_dir, &children); + std::cout << "Children Num: " << children.size() + << "; Status: " << s.ToString() + << "; Current dir: " << current_dir << std::endl; ret.Update(s); // If GetChildren() fails, continue the next search. -- GitLab From 278c0fbc7e4fad5644d2d79b4a48a4918d109dad Mon Sep 17 00:00:00 2001 From: mdfaijul Date: Thu, 11 Oct 2018 17:02:22 -0700 Subject: [PATCH 0094/1825] changed enum to enum class --- tensorflow/core/util/mkl_util.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/util/mkl_util.h b/tensorflow/core/util/mkl_util.h index 883fa612d5..a225850d21 100644 --- a/tensorflow/core/util/mkl_util.h +++ b/tensorflow/core/util/mkl_util.h @@ -104,10 +104,10 @@ typedef enum { Dim3d_I = 1 } MklDnnDims3D; -typedef enum { - QUANTIZED_VERSION = 0, +enum class MklQuantization { + QUANTIZED_VERSION, FP_VERSION, -} MklQuantization; +}; static const int kSmallBatchSize = 32; #ifdef INTEL_MKL_ML_ONLY -- GitLab From 988fae336c9146b1534a750edcd3b4905f207814 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Thu, 11 Oct 2018 17:35:13 -0700 Subject: [PATCH 0095/1825] Minor change on testNonExistingDirectory --- .../python/data/kernel_tests/matching_files_dataset_op_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index d811844cae..2a60b653d2 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -48,7 +48,7 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): def testNonExistingDirectory(self): """Test the MatchingFiles dataset with a non-existing directory""" - self.tearDown() + self.tmp_dir = os.path.join(self.tmp_dir, "nonexistingdir") dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) with self.cached_session() as sess: next_element = dataset.make_one_shot_iterator().get_next() -- GitLab From 503eee5bcaa5c5ff55acad024543babad36eb557 Mon Sep 17 00:00:00 2001 From: mdfaijul Date: Fri, 12 Oct 2018 03:03:22 -0700 Subject: [PATCH 0096/1825] fixed clang-format from target log --- tensorflow/core/ops/nn_ops.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/ops/nn_ops.cc b/tensorflow/core/ops/nn_ops.cc index ee0c5ce51d..199176e93f 100644 --- a/tensorflow/core/ops/nn_ops.cc +++ b/tensorflow/core/ops/nn_ops.cc @@ -704,8 +704,8 @@ REGISTER_OP("LRNGrad") REGISTER_OP("MaxPool") .Attr( - "T: {half, bfloat16, float, double, int32, int64, uint8, int16, int8, " - "uint16, qint8} = DT_FLOAT") + "T: {half, bfloat16, float, double, int32, int64, uint8, int16, int8, " + "uint16, qint8} = DT_FLOAT") .Attr("ksize: list(int) >= 4") .Attr("strides: list(int) >= 4") .Attr(GetPaddingAttrString()) @@ -716,8 +716,8 @@ REGISTER_OP("MaxPool") REGISTER_OP("MaxPoolV2") .Attr( - "T: {half, bfloat16, float, double, int32, int64, uint8, int16, int8, " - "uint16, qint8} = DT_FLOAT") + "T: {half, bfloat16, float, double, int32, int64, uint8, int16, int8, " + "uint16, qint8} = DT_FLOAT") .Attr(GetPaddingAttrString()) .Attr("data_format: {'NHWC', 'NCHW', 'NCHW_VECT_C'} = 'NHWC'") .Input("input: T") @@ -2262,8 +2262,8 @@ REGISTER_OP("_MklInputConversion") .Output("mkl_output_1: uint8") // All datatypes supported by element-wise ops .Attr( - "T: {half, float, double, uint8, int8, uint16, int16, int32, int64, " - "complex64, complex128}") + "T: {half, float, double, uint8, int8, uint16, int16, int32, int64, " + "complex64, complex128}") .Attr(GetConvnetDataFormat2D3DAttrString()) .SetShapeFn(shape_inference::UnknownShape) .Doc(R"doc( -- GitLab From 67d6b59b76f93496083b31569116880510f209ae Mon Sep 17 00:00:00 2001 From: Vijay Vasudevan Date: Fri, 12 Oct 2018 09:42:27 -0700 Subject: [PATCH 0097/1825] Update documentation, add brackets. --- tensorflow/core/kernels/conv_ops.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tensorflow/core/kernels/conv_ops.cc b/tensorflow/core/kernels/conv_ops.cc index 61ef217097..2b273d6ff2 100644 --- a/tensorflow/core/kernels/conv_ops.cc +++ b/tensorflow/core/kernels/conv_ops.cc @@ -739,14 +739,16 @@ void LaunchConv2DOp::operator()( To32Bit(transformed_filter.tensor())); Tensor transformed_output; - if (data_format == FORMAT_NHWC) + if (data_format == FORMAT_NHWC) { + // Only allocate temporary memory when a layout transformation is needed. OP_REQUIRES_OK( ctx, ctx->allocate_temp(DataTypeToEnum::value, ShapeFromFormat(FORMAT_NCHW, out_batch, out_rows, out_cols, out_depths), &transformed_output)); - else + } else { transformed_output = *output; + } auto input_ptr = AsDeviceMemory(input.template flat().data(), input.template flat().size()); -- GitLab From d14c6ed4ead3685638341b650641940e6190a0d3 Mon Sep 17 00:00:00 2001 From: Guozhong Zhuang Date: Fri, 12 Oct 2018 09:45:03 -0700 Subject: [PATCH 0098/1825] Clean out MKL_ML code from backward conv2D ops --- .../core/kernels/mkl_conv_grad_filter_ops.cc | 394 +----------------- .../core/kernels/mkl_conv_grad_input_ops.cc | 324 -------------- tensorflow/core/kernels/mkl_conv_ops.h | 8 - 3 files changed, 1 insertion(+), 725 deletions(-) diff --git a/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc b/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc index f406ad2ab5..eebd788545 100644 --- a/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc @@ -38,25 +38,17 @@ limitations under the License. #include "tensorflow/core/util/use_cudnn.h" #include "tensorflow/core/util/work_sharder.h" -#ifndef INTEL_MKL_ML_ONLY #include "mkldnn.hpp" +#include "tensorflow/core/util/mkl_util.h" using mkldnn::convolution_backward_weights; using mkldnn::memory; using mkldnn::prop_kind; using mkldnn::stream; -#else -#include "mkl_dnn.h" -#include "mkl_dnn_types.h" -#endif - -#include "tensorflow/core/util/mkl_util.h" namespace tensorflow { typedef Eigen::ThreadPoolDevice CPUDevice; -#ifndef INTEL_MKL_ML_ONLY - struct MklConvBwdFilterParams { memory::dims src_dims; memory::dims diff_filter_dims; @@ -358,388 +350,6 @@ class MklConvBwdFilterPrimitiveFactory : public MklPrimitiveFactory { } }; -#endif - -#ifdef INTEL_MKL_ML_ONLY - -template -class MklConv2DCustomBackpropFilterOp : public OpKernel { - public: - explicit MklConv2DCustomBackpropFilterOp(OpKernelConstruction* context) - : OpKernel(context) { - string data_format; - OP_REQUIRES_OK(context, context->GetAttr("data_format", &data_format)); - OP_REQUIRES(context, FormatFromString(data_format, &data_format_), - errors::InvalidArgument("Invalid data format")); - - OP_REQUIRES_OK(context, context->GetAttr("strides", &strides_)); - int stride_n = GetTensorDim(strides_, data_format_, 'N'); - int stride_c = GetTensorDim(strides_, data_format_, 'C'); - 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("padding", &padding_)); - } - - void Compute(OpKernelContext* context) override { - MklConv2DGradFilterOpContext mkl_context; - const Tensor& input = MklGetInput(context, 0); - GetMklShape(context, 0, &(mkl_context.input_shape)); - bool input_in_mkl_format = mkl_context.input_shape.IsMklTensor(); - - const Tensor& filter_sizes = MklGetInput(context, 1); - - const Tensor& out_backprop = MklGetInput(context, 2); - GetMklShape(context, 2, &(mkl_context.out_backprop_shape)); - bool out_backprop_in_mkl_format = - mkl_context.out_backprop_shape.IsMklTensor(); - - TensorShape input_shape, filter_shape, out_backprop_shape; - - OP_REQUIRES( - context, TensorShapeUtils::IsVector(filter_sizes.shape()), - errors::InvalidArgument( - "Conv2DCustomBackpropFilter: filter_sizes input must be 1-dim, " - "not ", - filter_sizes.dims())); - OP_REQUIRES_OK(context, TensorShapeUtils::MakeShape( - filter_sizes.vec(), &filter_shape)); - - ConvBackpropDimensions backprop_dims; - - // Generate shape for input if input is in MKL format. - if (input_in_mkl_format) { - OP_REQUIRES(context, mkl_context.input_shape.GetDimension() == 4, - errors::InvalidArgument( - "Conv2DCustomBackpropFilter: input size must be 4-dim")); - - MklSizesToTFSizes(context, data_format_, mkl_context.input_shape, - &input_shape); - } else { - input_shape = input.shape(); - } - - // Generate shape for outback prop if input is in MKL format. - if (out_backprop_in_mkl_format) { - OP_REQUIRES( - context, mkl_context.out_backprop_shape.GetDimension() == 4, - errors::InvalidArgument( - "Conv2DCustomBackpropFilter: outbackprop size must be 4-dim")); - - MklSizesToTFSizes(context, data_format_, mkl_context.out_backprop_shape, - &out_backprop_shape); - } else { - out_backprop_shape = out_backprop.shape(); - } - - OP_REQUIRES_OK(context, - ConvBackpropComputeDimensions( - "Conv2DCustomBackpropFilter", /*num_spatial_dims=*/2, - input_shape, filter_shape, out_backprop_shape, strides_, - padding_, data_format_, &backprop_dims)); - - int64 pad_top, pad_bottom; - int64 pad_left, pad_right; - OP_REQUIRES_OK(context, GetWindowedOutputSizeVerbose( - backprop_dims.spatial_dims[0].input_size, - backprop_dims.spatial_dims[0].filter_size, - backprop_dims.spatial_dims[0].stride, padding_, - &backprop_dims.spatial_dims[0].output_size, - &pad_top, &pad_bottom)); - OP_REQUIRES_OK(context, GetWindowedOutputSizeVerbose( - backprop_dims.spatial_dims[1].input_size, - backprop_dims.spatial_dims[1].filter_size, - backprop_dims.spatial_dims[1].stride, padding_, - &backprop_dims.spatial_dims[1].output_size, - &pad_left, &pad_right)); - - // Create MKL primitives for convolution filter grad - mkl_context.in_dims = input_in_mkl_format - ? mkl_context.input_shape.GetDimension() - : input.dims(); - mkl_context.out_dims = out_backprop_in_mkl_format - ? mkl_context.out_backprop_shape.GetDimension() - : out_backprop.dims(); - mkl_context.in_sizes[0] = - static_cast(backprop_dims.spatial_dims[1].input_size); - mkl_context.in_sizes[1] = - static_cast(backprop_dims.spatial_dims[0].input_size); - mkl_context.in_sizes[2] = static_cast(backprop_dims.in_depth); - mkl_context.in_sizes[3] = static_cast(backprop_dims.batch_size); - mkl_context.out_sizes[0] = - static_cast(backprop_dims.spatial_dims[1].output_size); - mkl_context.out_sizes[1] = - static_cast(backprop_dims.spatial_dims[0].output_size); - mkl_context.out_sizes[2] = static_cast(backprop_dims.out_depth); - mkl_context.out_sizes[3] = static_cast(backprop_dims.batch_size); - mkl_context.input_offsets[0] = static_cast(-pad_left); - mkl_context.input_offsets[1] = static_cast(-pad_top); - mkl_context.conv_strides[0] = - static_cast(backprop_dims.spatial_dims[1].stride); - mkl_context.conv_strides[1] = - static_cast(backprop_dims.spatial_dims[0].stride); - - GetStridesFromSizes(data_format_, mkl_context.in_strides, - mkl_context.in_sizes); - GetStridesFromSizes(data_format_, mkl_context.out_strides, - mkl_context.out_sizes); - - // MKL understands dimensions in 0, 1, 2, and 3 indices denotes - // filter cols, rows, input channels, and output depth/channels. - mkl_context.filter_dims = 4; - mkl_context.filter_sizes[0] = backprop_dims.spatial_dims[1].filter_size; - mkl_context.filter_sizes[1] = backprop_dims.spatial_dims[0].filter_size; - mkl_context.filter_sizes[2] = backprop_dims.in_depth; - mkl_context.filter_sizes[3] = backprop_dims.out_depth; - - // We want filter grad to be in TF format, so - // make the strides accordingly to reflect this fact. - // Note TF filter layout : (rows, cols, in_depth, out_depth), - // while row is the innermost dimension. - mkl_context.filter_strides[0] = - backprop_dims.out_depth * backprop_dims.in_depth; - mkl_context.filter_strides[1] = backprop_dims.out_depth * - backprop_dims.in_depth * - backprop_dims.spatial_dims[1].filter_size; - mkl_context.filter_strides[2] = backprop_dims.out_depth; - mkl_context.filter_strides[3] = 1; - - mkl_context.conv_strides[0] = backprop_dims.spatial_dims[1].stride; - mkl_context.conv_strides[1] = backprop_dims.spatial_dims[0].stride; - - // Create convolution-grad-filter primitive - CHECK_EQ(dnnConvolutionCreateBackwardFilter_F32( - &mkl_context.prim_conv_bwdfilter, nullptr, - dnnAlgorithmConvolutionDirect, mkl_context.in_dims, - mkl_context.in_sizes, mkl_context.out_sizes, - mkl_context.filter_sizes, mkl_context.conv_strides, - mkl_context.input_offsets, dnnBorderZeros), - E_SUCCESS); - - // Create the layouts for entities in received context. - mkl_context.MklCreateInputLayouts(context); - - // Mkl needs the entities in its native format. - // So create temporary tensors along with buffers to - // convert the received entities. - Tensor mkl_tmp_input_buf_tensor, mkl_tmp_out_backprop_buf_tensor; - // This preparation sets (1) dnnResourceSrc (2) dnnResourceDiffDst - mkl_context.MklPrepareInputs(context, &mkl_tmp_input_buf_tensor, - &mkl_tmp_out_backprop_buf_tensor); - - // Final conv-grad-filter should be in TF layout. - Tensor* grad_filter; - mkl_context.grad_filter_shape.SetMklTensor(false); - mkl_context.grad_filter_shape.SetTfLayout(mkl_context.filter_dims, - mkl_context.filter_sizes, - mkl_context.filter_strides); - AllocateOutputSetMklShape(context, 0, &grad_filter, filter_shape, - mkl_context.grad_filter_shape); - - // Need to set member variable for TF layout - mkl_context.lt_grad_filter = mkl_context.grad_filter_shape.GetTfLayout(); - - // MKL conv-grad-filter might produce grad in its internal layout - Tensor mkl_tmp_grad_filter_buf_tensor; - // This preparation sets conversion primitive if required - // and allocates temporary tensor and its buffer without doing conversions. - // Also sets (3) dnnResourceDiffFilter accordingly - mkl_context.MklPrepareGradFilter(context, grad_filter, - &mkl_tmp_grad_filter_buf_tensor); - - // After setting all the required dnnResources, ready for execution! - CHECK_EQ( - dnnExecute_F32(mkl_context.prim_conv_bwdfilter, mkl_context.conv_res), - E_SUCCESS); - - // Convert grad-filter to TF layout - if (mkl_context.convert_bwdfilter != nullptr) { - void* mkl_buf_convert_grad_filter = - const_cast(static_cast( - mkl_tmp_grad_filter_buf_tensor.flat().data())); - void* mkl_buf_grad_filter = const_cast( - static_cast(grad_filter->flat().data())); - CHECK_EQ(dnnConversionExecute_F32(mkl_context.convert_bwdfilter, - mkl_buf_convert_grad_filter, - mkl_buf_grad_filter), - E_SUCCESS); - } - - mkl_context.MklCleanup(); - } - - private: - typedef struct { - int in_dims; - size_t in_sizes[4]; - size_t in_strides[4]; - int out_dims; - size_t out_sizes[4]; - size_t out_strides[4]; - int filter_dims; - size_t filter_sizes[4]; - size_t filter_strides[4]; - int input_offsets[2]; - size_t conv_strides[2]; - MklShape input_shape, grad_filter_shape, out_backprop_shape; - dnnPrimitive_t prim_conv_bwdfilter = nullptr; - dnnPrimitive_t convert_bwdfilter = nullptr; - dnnLayout_t lt_input = nullptr; - dnnLayout_t lt_grad_filter = nullptr; - dnnLayout_t lt_out_backprop = nullptr; - void* conv_res[dnnResourceNumber]; - - void MklCleanup() { - // Cleanup member layouts and primitives except "lt_grad_filter_" - // which points to MklShape's TFLayout - bool input_in_mkl_format = input_shape.IsMklTensor(); - bool out_backprop_in_mkl_format = out_backprop_shape.IsMklTensor(); - if (!input_in_mkl_format) dnnLayoutDelete_F32(lt_input); - if (!out_backprop_in_mkl_format) dnnLayoutDelete_F32(lt_out_backprop); - if (convert_bwdfilter != nullptr) dnnDelete_F32(convert_bwdfilter); - dnnDelete_F32(prim_conv_bwdfilter); - } - - // Create MKL dnnLayout_t objects for tensors coming into the layer - void MklCreateInputLayouts(OpKernelContext* context) { - bool input_in_mkl_format = input_shape.IsMklTensor(); - if (input_in_mkl_format) { - lt_input = static_cast(input_shape.GetCurLayout()); - } else { - CHECK_EQ(dnnLayoutCreate_F32(<_input, in_dims, in_sizes, in_strides), - E_SUCCESS); - } - - bool out_backprop_in_mkl_format = out_backprop_shape.IsMklTensor(); - if (out_backprop_in_mkl_format) { - lt_out_backprop = - static_cast(out_backprop_shape.GetCurLayout()); - } else { - CHECK_EQ(dnnLayoutCreate_F32(<_out_backprop, out_dims, out_sizes, - out_strides), - E_SUCCESS); - } - } - - // Compare incoming tensor layouts with MKL preferred layouts and convert - // data to the preferred layout if necessary - void MklPrepareInputs(OpKernelContext* context, - Tensor* mkl_tmp_input_buf_tensor, - Tensor* mkl_tmp_out_backprop_buf_tensor) { - bool mkl_convert_input, mkl_convert_out_backprop; - dnnPrimitive_t mkl_prim_convert_input, mkl_prim_convert_out_backprop; - dnnLayout_t mkl_lt_internal_input, mkl_lt_internal_out_backprop; - void *mkl_buf_convert_input, *mkl_buf_convert_out_backprop; - - mkl_prim_convert_input = nullptr; - mkl_prim_convert_out_backprop = nullptr; - mkl_lt_internal_input = nullptr; - mkl_lt_internal_out_backprop = nullptr; - mkl_buf_convert_input = nullptr; - mkl_buf_convert_out_backprop = 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, prim_conv_bwdfilter, dnnResourceSrc), - E_SUCCESS); - mkl_convert_input = - !dnnLayoutCompare_F32(mkl_lt_internal_input, lt_input); - if (mkl_convert_input) { - CHECK_EQ(dnnConversionCreate_F32(&mkl_prim_convert_input, lt_input, - mkl_lt_internal_input), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_input_buf_tensor, mkl_lt_internal_input, - &mkl_buf_convert_input); - CHECK_EQ(dnnConversionExecute_F32(mkl_prim_convert_input, mkl_buf_input, - mkl_buf_convert_input), - E_SUCCESS); - dnnDelete_F32(mkl_prim_convert_input); - } - dnnLayoutDelete_F32(mkl_lt_internal_input); - - conv_res[dnnResourceSrc] = - (mkl_convert_input) ? mkl_buf_convert_input : mkl_buf_input; - - const Tensor& out_backprop = MklGetInput(context, 2); - void* mkl_buf_out_backprop = const_cast( - static_cast(out_backprop.flat().data())); - - CHECK_EQ(dnnLayoutCreateFromPrimitive_F32(&mkl_lt_internal_out_backprop, - prim_conv_bwdfilter, - dnnResourceDiffDst), - E_SUCCESS); - mkl_convert_out_backprop = - !dnnLayoutCompare_F32(mkl_lt_internal_out_backprop, lt_out_backprop); - if (mkl_convert_out_backprop) { - CHECK_EQ(dnnConversionCreate_F32(&mkl_prim_convert_out_backprop, - lt_out_backprop, - mkl_lt_internal_out_backprop), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_out_backprop_buf_tensor, - lt_out_backprop, &mkl_buf_convert_out_backprop); - CHECK_EQ(dnnConversionExecute_F32(mkl_prim_convert_out_backprop, - mkl_buf_out_backprop, - mkl_buf_convert_out_backprop), - E_SUCCESS); - dnnDelete_F32(mkl_prim_convert_out_backprop); - } - dnnLayoutDelete_F32(mkl_lt_internal_out_backprop); - - conv_res[dnnResourceDiffDst] = (mkl_convert_out_backprop) - ? mkl_buf_convert_out_backprop - : mkl_buf_out_backprop; - } - - void MklPrepareGradFilter(OpKernelContext* context, Tensor* grad_filter, - Tensor* mkl_tmp_grad_filter_buf_tensor) { - bool mkl_convert_grad_filter; - dnnLayout_t mkl_lt_internal_grad_filter = nullptr; - void* mkl_buf_convert_grad_filter = nullptr; - void* mkl_buf_grad_filter = const_cast( - static_cast(grad_filter->flat().data())); - CHECK_EQ(dnnLayoutCreateFromPrimitive_F32(&mkl_lt_internal_grad_filter, - prim_conv_bwdfilter, - dnnResourceDiffFilter), - E_SUCCESS); - mkl_convert_grad_filter = - !dnnLayoutCompare_F32(mkl_lt_internal_grad_filter, lt_grad_filter); - if (mkl_convert_grad_filter) { - CHECK_EQ(dnnConversionCreate_F32(&convert_bwdfilter, - mkl_lt_internal_grad_filter, - lt_grad_filter), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_grad_filter_buf_tensor, - mkl_lt_internal_grad_filter, - &mkl_buf_convert_grad_filter); - } - dnnLayoutDelete_F32(mkl_lt_internal_grad_filter); - - conv_res[dnnResourceDiffFilter] = (mkl_convert_grad_filter) - ? mkl_buf_convert_grad_filter - : mkl_buf_grad_filter; - } - } MklConv2DGradFilterOpContext; - - std::vector strides_; - Padding padding_; - TensorFormat data_format_; -}; - -#define REGISTER_MKL_FILTER_KERNELS(T) \ - REGISTER_KERNEL_BUILDER(Name("_MklConv2DBackpropFilter") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConv2DCustomBackpropFilterOp); -TF_CALL_float(REGISTER_MKL_FILTER_KERNELS); -#undef REGISTER_MKL_FILTER_KERNELS - -#else - template class MklConvCustomBackpropFilterOp : public MklConvBackpropCommonOp { @@ -1080,8 +690,6 @@ class MklConvCustomBackpropFilterOp TF_CALL_float(REGISTER_MKL_FILTER_KERNELS); #undef REGISTER_MKL_FILTER_KERNELS -#endif // INTEL_MKL_ML_ONLY - } // namespace tensorflow #endif // INTEL_MKL diff --git a/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc b/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc index a501ce2c93..1f9e6abe44 100644 --- a/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc @@ -23,10 +23,6 @@ limitations under the License. #define EIGEN_USE_THREADS #include #include -#ifdef INTEL_MKL_ML_ONLY -#include "mkl_dnn.h" -#include "mkl_dnn_types.h" -#endif #include "tensorflow/core/framework/numeric_op.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" @@ -46,19 +42,15 @@ limitations under the License. #include "tensorflow/core/util/use_cudnn.h" #include "tensorflow/core/util/work_sharder.h" -#ifndef INTEL_MKL_ML_ONLY #include "mkldnn.hpp" using mkldnn::convolution_backward_data; using mkldnn::prop_kind; using mkldnn::stream; -#endif namespace tensorflow { typedef Eigen::ThreadPoolDevice CPUDevice; -#ifndef INTEL_MKL_ML_ONLY - /// utility classes enabling primitive reuse for backward conv ops. struct MklConvBwdInputParams { memory::dims diff_src_dims; @@ -293,320 +285,6 @@ class MklConvBwdInputPrimitiveFactory : public MklPrimitiveFactory { } }; -#endif - -#ifdef INTEL_MKL_ML_ONLY - -template -class MklConv2DCustomBackpropInputOp : public OpKernel { - public: - ~MklConv2DCustomBackpropInputOp() {} - explicit MklConv2DCustomBackpropInputOp(OpKernelConstruction* context) - : OpKernel(context) { - string dataformat; - OP_REQUIRES_OK(context, context->GetAttr("data_format", &dataformat)); - OP_REQUIRES(context, FormatFromString(dataformat, &data_format), - errors::InvalidArgument("Invalid data format")); - OP_REQUIRES_OK(context, context->GetAttr("strides", &strides)); - int stride_n = GetTensorDim(strides, data_format, 'N'); - int stride_c = GetTensorDim(strides, data_format, 'C'); - 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("padding", &padding)); - } - - void Compute(OpKernelContext* context) override { - MklConvBackInputOpContext mkl_context; - const Tensor& input = MklGetInput(context, 0); - const Tensor& filter = MklGetInput(context, 1); - - GetMklShape(context, 1, &(mkl_context.filter_shape)); - bool filter_in_mkl_format = mkl_context.filter_shape.IsMklTensor(); - - const Tensor& out_backprop = MklGetInput(context, 2); - GetMklShape(context, 2, &(mkl_context.outback_shape)); - bool outback_in_mkl_format = mkl_context.outback_shape.IsMklTensor(); - - TensorShape input_shape, filter_shape, outback_shape; - - // Generate input shape. - OP_REQUIRES( - context, TensorShapeUtils::IsVector(input.shape()), - errors::InvalidArgument( - "Conv2DBackpropInput: input_sizes input must be 1-dim, not ", - input.dims())); - OP_REQUIRES_OK( - context, TensorShapeUtils::MakeShape(input.vec(), &input_shape)); - - // Generate shape for filter prop if input is in MKL format. - if (filter_in_mkl_format) { - OP_REQUIRES(context, mkl_context.filter_shape.GetDimension() == 4, - errors::InvalidArgument( - "Conv2DCustomBackpropInput: size must be 4-dim")); - - const int64* filter_sizes = - (const int64*)mkl_context.filter_shape.GetSizes(); - const int64 filter_dims = mkl_context.filter_shape.GetDimension(); - - OP_REQUIRES_OK(context, TensorShapeUtils::MakeShape( - filter_sizes, filter_dims, &filter_shape)); - } else { - filter_shape = filter.shape(); - } - - // Generate shape for outback prop if input is in MKL format. - if (outback_in_mkl_format) { - OP_REQUIRES(context, mkl_context.outback_shape.GetDimension() == 4, - errors::InvalidArgument( - "Conv2DCustomBackpropInput: size must be 4-dim")); - - MklSizesToTFSizes(context, data_format, mkl_context.outback_shape, - &outback_shape); - } else { - outback_shape = out_backprop.shape(); - } - - ConvBackpropDimensions dims; - OP_REQUIRES_OK( - context, - ConvBackpropComputeDimensions( - "Conv2DCustomBackpropInput", /*num_spatial_dims=*/2, input_shape, - filter_shape, outback_shape, strides, padding, data_format, &dims)); - - int64 pad_top, pad_bottom; - int64 pad_left, pad_right; - OP_REQUIRES_OK( - context, - GetWindowedOutputSizeVerbose( - dims.spatial_dims[0].input_size, dims.spatial_dims[0].filter_size, - dims.spatial_dims[0].stride, padding, - &dims.spatial_dims[0].output_size, &pad_top, &pad_bottom)); - OP_REQUIRES_OK( - context, - GetWindowedOutputSizeVerbose( - dims.spatial_dims[1].input_size, dims.spatial_dims[1].filter_size, - dims.spatial_dims[1].stride, padding, - &dims.spatial_dims[1].output_size, &pad_left, &pad_right)); - - mkl_context.in_dims = 4; - - mkl_context.in_sizes[0] = - static_cast(dims.spatial_dims[1].input_size); - mkl_context.in_sizes[1] = - static_cast(dims.spatial_dims[0].input_size); - mkl_context.in_sizes[2] = static_cast(dims.in_depth); - mkl_context.in_sizes[3] = static_cast(dims.batch_size); - - mkl_context.out_sizes[0] = - static_cast(dims.spatial_dims[1].output_size); - mkl_context.out_sizes[1] = - static_cast(dims.spatial_dims[0].output_size); - mkl_context.out_sizes[2] = static_cast(dims.out_depth); - mkl_context.out_sizes[3] = static_cast(dims.batch_size); - - mkl_context.input_offset[0] = static_cast(-pad_left); - mkl_context.input_offset[1] = static_cast(-pad_top); - - mkl_context.conv_strides[0] = - static_cast(dims.spatial_dims[1].stride); - mkl_context.conv_strides[1] = - static_cast(dims.spatial_dims[0].stride); - - GetStridesFromSizes(data_format, mkl_context.out_strides, - mkl_context.out_sizes); - GetStridesFromSizes(data_format, mkl_context.in_strides, - mkl_context.in_sizes); - - mkl_context.filter_size[0] = dims.spatial_dims[1].filter_size; - mkl_context.filter_size[1] = dims.spatial_dims[0].filter_size; - mkl_context.filter_size[2] = dims.in_depth; - mkl_context.filter_size[3] = dims.out_depth; - - mkl_context.filter_stride[0] = - mkl_context.filter_size[2] * mkl_context.filter_size[3]; - mkl_context.filter_stride[1] = mkl_context.filter_size[2] * - mkl_context.filter_size[0] * - mkl_context.filter_size[3]; - mkl_context.filter_stride[2] = mkl_context.filter_size[3]; - mkl_context.filter_stride[3] = 1; - - CHECK_EQ( - dnnConvolutionCreateBackwardData_F32( - &mkl_context.prim_bwddata, NULL, dnnAlgorithmConvolutionDirect, - mkl_context.in_dims, mkl_context.in_sizes, mkl_context.out_sizes, - mkl_context.filter_size, mkl_context.conv_strides, - mkl_context.input_offset, dnnBorderZeros), - E_SUCCESS); - - // Allocate output tensor and shape - TensorShape mkl_out_shape; - MklShape mklOutputShape; - mklOutputShape.SetMklTensor(true); - mklOutputShape.SetMklLayout(mkl_context.prim_bwddata, dnnResourceDiffSrc); - mklOutputShape.SetTfLayout(mkl_context.in_dims, mkl_context.in_sizes, - mkl_context.in_strides); - // MKL might change the dimension ordering. - // Create mapping to recover the original TF dimension order - mklOutputShape.SetTfDimOrder(mkl_context.in_dims, data_format); - - Tensor* in_backprop = nullptr; - mkl_out_shape.AddDim(dnnLayoutGetMemorySize_F32(static_cast( - mklOutputShape.GetMklLayout())) / - sizeof(T)); - AllocateOutputSetMklShape(context, 0, &in_backprop, mkl_out_shape, - mklOutputShape); - - mkl_context.conv_res[dnnResourceDiffSrc] = - static_cast(const_cast(in_backprop->flat().data())); - - mkl_context.MklCreateInputLayouts(context); - Tensor mkl_tmp_outbackprop_buf_tensor, mkl_tmp_filter_buf_tensor; - mkl_context.MklPrepareConvolutionInputs( - context, &mkl_tmp_outbackprop_buf_tensor, &mkl_tmp_filter_buf_tensor); - - CHECK_EQ(dnnExecute_F32(mkl_context.prim_bwddata, mkl_context.conv_res), - E_SUCCESS); - mkl_context.MklCleanup(); - } - - private: - typedef struct { - int in_dims; - size_t in_sizes[4]; - size_t in_strides[4]; - size_t out_sizes[4]; - size_t out_strides[4]; - int input_offset[2]; - size_t filter_size[4]; - size_t filter_stride[4]; - size_t conv_strides[2]; - MklShape filter_shape, outback_shape; - dnnPrimitive_t prim_bwddata; - void* conv_res[dnnResourceNumber]; - dnnLayout_t lt_filter, lt_outbackprop; - - // Create MKL dnnLayout_t objects for tensors coming into the layer - void MklCreateInputLayouts(OpKernelContext* context) { - bool filter_in_mkl_format = filter_shape.IsMklTensor(); - bool outback_in_mkl_format = outback_shape.IsMklTensor(); - if (filter_in_mkl_format) { - lt_filter = (dnnLayout_t)filter_shape.GetCurLayout(); - } else { - CHECK_EQ(dnnLayoutCreate_F32(<_filter, in_dims, filter_size, - filter_stride), - E_SUCCESS); - } - - if (outback_in_mkl_format) { - lt_outbackprop = (dnnLayout_t)outback_shape.GetCurLayout(); - } else { - CHECK_EQ(dnnLayoutCreate_F32(<_outbackprop, in_dims, out_sizes, - out_strides), - E_SUCCESS); - } - } - - // Compare incoming input tensor layouts with MKL preferred layouts and - // convert data to the preferred layout if necessary - void MklPrepareConvolutionInputs(OpKernelContext* context, - Tensor* mkl_tmp_outbackprop_buf_tensor, - Tensor* mkl_tmp_filter_buf_tensor) { - dnnPrimitive_t mkl_convert_filter = nullptr, - mkl_convert_outbackprop = nullptr; - void *mkl_filter_buf = nullptr, *mkl_outbackprop_buf = nullptr; - dnnLayout_t mkl_lt_filter_internal = nullptr, - mkl_lt_outbackprop_internal = nullptr; - CHECK_EQ(dnnLayoutCreateFromPrimitive_F32( - &mkl_lt_filter_internal, prim_bwddata, dnnResourceFilter), - E_SUCCESS); - - const Tensor& filter = MklGetInput(context, 1); - - CHECK_EQ( - dnnLayoutCreateFromPrimitive_F32(&mkl_lt_outbackprop_internal, - prim_bwddata, dnnResourceDiffDst), - E_SUCCESS); - if (!dnnLayoutCompare_F32(mkl_lt_filter_internal, lt_filter)) { - // Create conversion primitive - CHECK_EQ(dnnConversionCreate_F32(&mkl_convert_filter, lt_filter, - mkl_lt_filter_internal), - E_SUCCESS); - - AllocTmpBuffer(context, mkl_tmp_filter_buf_tensor, - mkl_lt_filter_internal, &mkl_filter_buf); - CHECK_EQ( - dnnConversionExecute_F32( - mkl_convert_filter, - static_cast(const_cast(filter.flat().data())), - mkl_filter_buf), - E_SUCCESS); - - // Assign filter buf to resources[] for convolution. - conv_res[dnnResourceFilter] = mkl_filter_buf; - dnnDelete_F32(mkl_convert_filter); - } else { - // If we do not need any layout conversion for filter, then - // we directly assign input filter to resources[]. - conv_res[dnnResourceFilter] = - static_cast(const_cast(filter.flat().data())); - } - dnnLayoutDelete_F32(mkl_lt_filter_internal); - const Tensor& out_backprop = MklGetInput(context, 2); - // -- - // We do similar steps as above for outputbackprop. - if (!dnnLayoutCompare_F32(mkl_lt_outbackprop_internal, lt_outbackprop)) { - CHECK_EQ( - dnnConversionCreate_F32(&mkl_convert_outbackprop, lt_outbackprop, - mkl_lt_outbackprop_internal), - E_SUCCESS); - AllocTmpBuffer(context, mkl_tmp_outbackprop_buf_tensor, - mkl_lt_outbackprop_internal, &mkl_outbackprop_buf); - - CHECK_EQ(dnnConversionExecute_F32(mkl_convert_outbackprop, - static_cast(const_cast( - out_backprop.flat().data())), - mkl_outbackprop_buf), - E_SUCCESS); - - conv_res[dnnResourceDiffDst] = mkl_outbackprop_buf; - dnnDelete_F32(mkl_convert_outbackprop); - } else { - conv_res[dnnResourceDiffDst] = - static_cast(const_cast(out_backprop.flat().data())); - } - dnnLayoutDelete_F32(mkl_lt_outbackprop_internal); - } - - // Cleanup member layouts and primitives - void MklCleanup() { - bool filter_in_mkl_format = filter_shape.IsMklTensor(); - bool outback_in_mkl_format = outback_shape.IsMklTensor(); - if (!filter_in_mkl_format) dnnLayoutDelete_F32(lt_filter); - if (!outback_in_mkl_format) dnnLayoutDelete_F32(lt_outbackprop); - dnnDelete_F32(prim_bwddata); - } - } MklConvBackInputOpContext; - - std::vector strides; - Padding padding; - TensorFormat data_format; -}; - -#define REGISTER_MKL_CPU_KERNELS(T) \ - REGISTER_KERNEL_BUILDER(Name("_MklConv2DBackpropInput") \ - .Device(DEVICE_CPU) \ - .TypeConstraint("T") \ - .Label(mkl_op_registry::kMklOpLabel), \ - MklConv2DCustomBackpropInputOp); - -TF_CALL_float(REGISTER_MKL_CPU_KERNELS); -#undef REGISTER_MKL_CPU_KERNELS - -#else - template class MklConvCustomBackpropInputOp : public MklConvBackpropCommonOp { public: @@ -881,7 +559,5 @@ class MklConvCustomBackpropInputOp : public MklConvBackpropCommonOp { TF_CALL_float(REGISTER_MKL_CPU_KERNELS); #undef REGISTER_MKL_CPU_KERNELS -#endif // INTEL_MKL_ML_ONLY - } // namespace tensorflow #endif // INTEL_MKL diff --git a/tensorflow/core/kernels/mkl_conv_ops.h b/tensorflow/core/kernels/mkl_conv_ops.h index 01cc606f41..28d521c9be 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.h +++ b/tensorflow/core/kernels/mkl_conv_ops.h @@ -37,23 +37,16 @@ limitations under the License. #include "tensorflow/core/platform/macros.h" #include "tensorflow/core/util/padding.h" #include "tensorflow/core/util/tensor_format.h" - #include "tensorflow/core/util/mkl_util.h" - -#ifndef INTEL_MKL_ML_ONLY #include "mkldnn.hpp" using mkldnn::prop_kind; using mkldnn::stream; - using mkldnn::convolution_direct; using mkldnn::convolution_forward; -#endif namespace tensorflow { -#ifndef INTEL_MKL_ML_ONLY - class MklDnnConvUtil { protected: OpKernelContext* context_; // We don't own this. @@ -543,7 +536,6 @@ class MklConvBackpropCommonOp : public OpKernel { TensorFormat data_format_; // NCHW or NHWC }; -#endif // INTEL_MKL_ML_ONLY ///////////////////////////////////////////////////////////////////// /// Dummy Mkl op that is just used for operators that are intermediate -- GitLab From 3516b82a48699dd7f0b2464d6e78a84ba32c1564 Mon Sep 17 00:00:00 2001 From: Guozhong Zhuang Date: Fri, 12 Oct 2018 10:30:10 -0700 Subject: [PATCH 0099/1825] adjust headers inclusion order per PR review recommenation --- tensorflow/core/kernels/mkl_conv_ops.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/kernels/mkl_conv_ops.h b/tensorflow/core/kernels/mkl_conv_ops.h index 28d521c9be..ba0fb48b7b 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.h +++ b/tensorflow/core/kernels/mkl_conv_ops.h @@ -35,9 +35,9 @@ limitations under the License. #include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/macros.h" +#include "tensorflow/core/util/mkl_util.h" #include "tensorflow/core/util/padding.h" #include "tensorflow/core/util/tensor_format.h" -#include "tensorflow/core/util/mkl_util.h" #include "mkldnn.hpp" using mkldnn::prop_kind; -- GitLab From a0653f833684787bcba74ee1870b6c81b016f42a Mon Sep 17 00:00:00 2001 From: Guozhong Zhuang Date: Fri, 12 Oct 2018 11:05:23 -0700 Subject: [PATCH 0100/1825] adjust headers inclusion order per review suggestion --- tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc | 5 ++--- tensorflow/core/kernels/mkl_conv_grad_input_ops.cc | 3 +-- tensorflow/core/kernels/mkl_conv_ops.h | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc b/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc index eebd788545..c1b182be4a 100644 --- a/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc @@ -20,6 +20,7 @@ limitations under the License. #include #include +#include "mkldnn.hpp" #include "tensorflow/core/framework/numeric_op.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" @@ -33,14 +34,12 @@ limitations under the License. #include "tensorflow/core/lib/gtl/array_slice.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/macros.h" +#include "tensorflow/core/util/mkl_util.h" #include "tensorflow/core/util/padding.h" #include "tensorflow/core/util/tensor_format.h" #include "tensorflow/core/util/use_cudnn.h" #include "tensorflow/core/util/work_sharder.h" -#include "mkldnn.hpp" -#include "tensorflow/core/util/mkl_util.h" - using mkldnn::convolution_backward_weights; using mkldnn::memory; using mkldnn::prop_kind; diff --git a/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc b/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc index 1f9e6abe44..786a30bb10 100644 --- a/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc +++ b/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc @@ -23,6 +23,7 @@ limitations under the License. #define EIGEN_USE_THREADS #include #include +#include "mkldnn.hpp" #include "tensorflow/core/framework/numeric_op.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" @@ -42,8 +43,6 @@ limitations under the License. #include "tensorflow/core/util/use_cudnn.h" #include "tensorflow/core/util/work_sharder.h" -#include "mkldnn.hpp" - using mkldnn::convolution_backward_data; using mkldnn::prop_kind; using mkldnn::stream; diff --git a/tensorflow/core/kernels/mkl_conv_ops.h b/tensorflow/core/kernels/mkl_conv_ops.h index ba0fb48b7b..e6989d884d 100644 --- a/tensorflow/core/kernels/mkl_conv_ops.h +++ b/tensorflow/core/kernels/mkl_conv_ops.h @@ -20,6 +20,7 @@ limitations under the License. #include #include +#include "mkldnn.hpp" #include "tensorflow/core/framework/numeric_op.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/register_types.h" @@ -38,12 +39,11 @@ limitations under the License. #include "tensorflow/core/util/mkl_util.h" #include "tensorflow/core/util/padding.h" #include "tensorflow/core/util/tensor_format.h" -#include "mkldnn.hpp" -using mkldnn::prop_kind; -using mkldnn::stream; using mkldnn::convolution_direct; using mkldnn::convolution_forward; +using mkldnn::prop_kind; +using mkldnn::stream; namespace tensorflow { -- GitLab From a45b907f6b03ec76f65512657d7b6231eb3c7152 Mon Sep 17 00:00:00 2001 From: Deepak B Date: Fri, 12 Oct 2018 13:20:20 -0700 Subject: [PATCH 0101/1825] Create ISSUES.md --- ISSUES.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 ISSUES.md diff --git a/ISSUES.md b/ISSUES.md new file mode 100644 index 0000000000..f44363c8b3 --- /dev/null +++ b/ISSUES.md @@ -0,0 +1,6 @@ +If you open a GitHub Issue, here is our policy: +1. It must be a bug/performance issue or a feature request or a build issue or a documentation issue (for small doc fixes please send a PR instead). +2. Make sure the Issue Template is filled out. +3. The issue should be related to the repo it is created in. + +**Here's why we have this policy:** We want to focus on the work that benefits the whole community, e.g., fixing bugs and adding features. Individual support should be seeked on StackOverflow or other non-GitHub channels. It helps us to address bugs and feature requests in a timely manner. -- GitLab From 7f77295b8aab2841627ceef6d70a5799df93f10f Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Fri, 12 Oct 2018 15:43:07 -0700 Subject: [PATCH 0102/1825] Refactor TransposeTensor and PrepareTensor for shape. Add more checks to reshape and transpose functions and converters. Improve tests. --- .../contrib/tensorrt/convert/convert_nodes.cc | 297 +++++++++++------- .../tensorrt/test/reshape_transpose_test.py | 182 ++++++++++- 2 files changed, 362 insertions(+), 117 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index e2ed372f12..b3f5fcd4f9 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -650,8 +650,11 @@ using OpConverter = class Converter { public: explicit Converter(nvinfer1::INetworkDefinition* trt_network, - TRTWeightStore* ws, bool fp16) - : trt_network_(trt_network), weight_store_(ws), fp16_(fp16) { + TRTWeightStore* ws, bool fp16, int max_batch_size) + : trt_network_(trt_network), + weight_store_(ws), + fp16_(fp16), + max_batch_size_(max_batch_size) { this->register_op_converters(); } @@ -669,6 +672,8 @@ class Converter { // TODO(aaroey): fix all the namings. bool isFP16() { return fp16_; } + int GetMaxBatchSize() { return max_batch_size_; } + TRT_ShapedWeights get_temp_weights_like(const TRT_ShapedWeights& weights) { return this->get_temp_weights(weights.type_, weights.shape_); } @@ -726,18 +731,23 @@ class Converter { return trt_tensors_.insert({name, TRT_TensorOrWeights(tensor)}).second; } - nvinfer1::ITensor* TransposeTensor(nvinfer1::ITensor* input_tensor, - const std::vector& order) { + tensorflow::Status TransposeTensor(nvinfer1::ITensor* input_tensor, + const std::vector& order, + const nvinfer1::ITensor** output_tensor) { const auto dims = input_tensor->getDimensions(); - // TODO(jie): change the return to status and properly exit - if (order.size() - 1 != size_t(dims.nbDims)) - LOG(ERROR) << "Dimension does not match, fail gracefully"; + if (order.size() - 1 != size_t(dims.nbDims)) { + return tensorflow::errors::InvalidArgument( + "Rank of perm for transpose does not match with that of the input."); + } + if (order[0] != 0) { + return tensorflow::errors::Unimplemented( + "Transpose at batch dimension is not supported."); + } nvinfer1::IShuffleLayer* layer = this->network()->addShuffle(*input_tensor); - if (layer == nullptr) { - return nullptr; - } + TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Transpose"); + nvinfer1::Permutation permutation; for (int32_t i = 0; i < dims.nbDims; ++i) { permutation.order[i] = order[i + 1] - 1; @@ -751,7 +761,52 @@ class Converter { reshape_dims.type[i] = dims.type[i]; } layer->setReshapeDimensions(reshape_dims); - return layer->getOutput(0); + + *output_tensor = layer->getOutput(0); + return tensorflow::Status::OK(); + } + + // Helper function converts input into tensor with shape specified by dims. + tensorflow::Status PrepareTensorForShape(const TRT_TensorOrWeights& input, + const nvinfer1::Dims& dims, + const nvinfer1::ITensor** tensor) { + // If -1 is not used for one of the dims, we can check if the shapes are + // compatible. + bool can_check_shapes = true; + for (int i = 0; i < dims.nbDims; i++) { + if (dims.d[i] == -1) { + can_check_shapes = false; + break; + } + } + if (can_check_shapes && + GetShapeSize(input.shape()) != GetShapeSize(dims)) { + return tensorflow::errors::InvalidArgument( + "Reshape shapes are not compatible."); + } + + if (input.is_tensor()) { + if (DimsEqual(input.shape(), dims)) { + *tensor = input.tensor(); + } else { + nvinfer1::IShuffleLayer* layer = this->network()->addShuffle( + *const_cast(input.tensor())); + TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Reshape"); + layer->setReshapeDimensions(dims); + *tensor = layer->getOutput(0); + } + } else { +#if NV_TENSORRT_MAJOR > 3 + nvinfer1::IConstantLayer* layer = + this->network()->addConstant(dims, input.weights()); + TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Reshape"); + *tensor = layer->getOutput(0); +#else + return tensorflow::errors::Unimplemented( + "Can't reshape constant. Please upgrade to TRT 4 or above."); +#endif + } + return tensorflow::Status::OK(); } private: @@ -767,6 +822,8 @@ class Converter { bool fp16_; + int max_batch_size_; + void register_op_converters(); tensorflow::Status get_inputs(const tensorflow::NodeDef& node_def, @@ -1140,9 +1197,10 @@ tensorflow::Status BinaryTensorOpWeight( } permutation[1] = dims_t.nbDims; permutation[dims_t.nbDims] = 1; - tensor = ctx.TransposeTensor(const_cast(tensor), - permutation); - TFTRT_RETURN_ERROR_IF_NULLPTR(tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(tensor), + permutation, + &tensor)); } else { return tensorflow::errors::InvalidArgument( "Transpose cannot be applied, " + node_def.name()); @@ -1203,15 +1261,18 @@ tensorflow::Status BinaryTensorOpWeight( scale_weights, power_weights); TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); - nvinfer1::ITensor* output_tensor = layer->getOutput(0); + const nvinfer1::ITensor* output_tensor = layer->getOutput(0); // transpose back dimension if (permutation_flag) { - output_tensor = ctx.TransposeTensor(output_tensor, permutation); - TFTRT_RETURN_ERROR_IF_NULLPTR(output_tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(output_tensor), + permutation, + &output_tensor)); } // Pass the output - outputs->push_back(TRT_TensorOrWeights(output_tensor)); + outputs->push_back(TRT_TensorOrWeights( + const_cast(output_tensor))); return tensorflow::Status::OK(); } @@ -1229,9 +1290,10 @@ tensorflow::Status ConvertConv2DHelper( int w_index = 3; auto data_format = attrs.get("data_format"); if (data_format == "NHWC") { - tensor = ctx.TransposeTensor(const_cast(tensor), - {0, 3, 1, 2}); - TFTRT_RETURN_ERROR_IF_NULLPTR(tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(tensor), + {0, 3, 1, 2}, + &tensor)); h_index = 1; w_index = 2; // TODO(jie): transpose it @@ -1309,15 +1371,18 @@ tensorflow::Status ConvertConv2DHelper( layer->setPadding({padding[0].first, padding[1].first}); layer->setName(node_def.name().c_str()); layer->setNbGroups(num_groups); - nvinfer1::ITensor* output_tensor = layer->getOutput(0); + 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! - output_tensor = ctx.TransposeTensor(output_tensor, {0, 2, 3, 1}); - TFTRT_RETURN_ERROR_IF_NULLPTR(output_tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(output_tensor), + {0, 2, 3, 1}, + &output_tensor)); } - outputs->push_back(TRT_TensorOrWeights(output_tensor)); + outputs->push_back(TRT_TensorOrWeights( + const_cast(output_tensor))); return tensorflow::Status::OK(); } @@ -1335,39 +1400,6 @@ tensorflow::Status ConvertConv2DHelper( node_def.name()); } -// Helper function converts input into tensor with shape specified by dims. -bool PrepareTensorForShape(Converter& ctx, const TRT_TensorOrWeights& input, - const nvinfer1::Dims& dims, - const nvinfer1::ITensor** tensor) { - if (input.is_tensor()) { - if (DimsEqual(input.shape(), dims)) { - *tensor = input.tensor(); - } else { - nvinfer1::IShuffleLayer* layer = ctx.network()->addShuffle( - *const_cast(input.tensor())); - if (layer != nullptr) { - layer->setReshapeDimensions(dims); - *tensor = layer->getOutput(0); - } else { - return false; - } - } - } else { -#if NV_TENSORRT_MAJOR > 3 - nvinfer1::IConstantLayer* layer = - ctx.network()->addConstant(dims, input.weights()); - if (layer != nullptr) { - *tensor = layer->getOutput(0); - } else { - return false; - } -#else - return false; -#endif - } - return true; -} - tensorflow::Status BinaryTensorOpTensor( Converter& ctx, const tensorflow::NodeDef& node_def, const TRT_TensorOrWeights& operand_l, const TRT_TensorOrWeights& operand_r, @@ -1396,10 +1428,8 @@ tensorflow::Status BinaryTensorOpTensor( node_def.op() + ", at: " + node_def.name()); } - TFTRT_RETURN_ERROR_IF_FALSE( - PrepareTensorForShape(ctx, operand_l, dim_l, &tensor_l), node_def.name()); - TFTRT_RETURN_ERROR_IF_FALSE( - PrepareTensorForShape(ctx, operand_r, dim_r, &tensor_r), node_def.name()); + TF_RETURN_IF_ERROR(ctx.PrepareTensorForShape(operand_l, dim_l, &tensor_l)); + TF_RETURN_IF_ERROR(ctx.PrepareTensorForShape(operand_r, dim_r, &tensor_r)); // get trt type & shape TFAttrs attrs(node_def); @@ -1487,8 +1517,15 @@ tensorflow::Status ConvertTranspose( perm[i] = weights_ptr[i]; } - nvinfer1::ITensor* output_tensor = ctx.TransposeTensor(input_tensor, perm); - outputs->push_back(TRT_TensorOrWeights(output_tensor)); + if (perm[0] != 0) { + return tensorflow::errors::Unimplemented( + "Transpose at batch dimension is not supported, at", node_def.name()); + } + + const nvinfer1::ITensor* output_tensor = nullptr; + TF_RETURN_IF_ERROR(ctx.TransposeTensor(input_tensor, perm, &output_tensor)); + outputs->push_back(TRT_TensorOrWeights( + const_cast(output_tensor))); return tensorflow::Status::OK(); } @@ -1502,6 +1539,12 @@ tensorflow::Status ConvertReshape( } TRT_ShapedWeights weights = inputs.at(1).weights(); + if (weights.count() == 0) { + return tensorflow::errors::Unimplemented( + "Reshape to shape=[] is not supported, at", node_def.name()); + } + + // Get new_shape const int* weights_ptr = static_cast(const_cast( weights.GetValues())); nvinfer1::Dims new_shape; @@ -1511,10 +1554,22 @@ tensorflow::Status ConvertReshape( new_shape.d[i-1] = weights_ptr[i]; } - const nvinfer1::ITensor* output_tensor; - TFTRT_RETURN_ERROR_IF_FALSE( - PrepareTensorForShape(ctx, inputs.at(0), new_shape, &output_tensor), - node_def.name()); + // Check that batch dimension doesn't change + const nvinfer1::Dims input_shape = inputs.at(0).shape(); + if (weights_ptr[0] == -1) { + // Product of input shape should equal product of new_shape + if (GetShapeSize(input_shape) != GetShapeSize(new_shape)) { + return tensorflow::errors::Unimplemented( + "Reshape on the batch dimension is not supported."); + } + } else if (weights_ptr[0] != ctx.GetMaxBatchSize()) { + return tensorflow::errors::Unimplemented( + "Reshape on the batch dimension is not supported."); + } + + const nvinfer1::ITensor* output_tensor = nullptr; + TF_RETURN_IF_ERROR( + ctx.PrepareTensorForShape(inputs.at(0), new_shape, &output_tensor)); outputs->push_back(TRT_TensorOrWeights( const_cast(output_tensor))); return tensorflow::Status::OK(); @@ -1549,9 +1604,10 @@ tensorflow::Status ConvertPool(Converter& ctx, if (data_format == "NHWC") { h_index = 1; w_index = 2; - tensor = ctx.TransposeTensor(const_cast(tensor), - {0, 3, 1, 2}); - TFTRT_RETURN_ERROR_IF_NULLPTR(tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(tensor), + {0, 3, 1, 2}, + &tensor)); } nvinfer1::PoolingType type; @@ -1607,13 +1663,16 @@ tensorflow::Status ConvertPool(Converter& ctx, layer->setStride(stride); layer->setPadding({padding[0].first, padding[1].first}); layer->setName(node_def.name().c_str()); - nvinfer1::ITensor* output_tensor = layer->getOutput(0); + const nvinfer1::ITensor* output_tensor = layer->getOutput(0); if (data_format == "NHWC") { - output_tensor = ctx.TransposeTensor(output_tensor, {0, 2, 3, 1}); - TFTRT_RETURN_ERROR_IF_NULLPTR(output_tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(output_tensor), + {0, 2, 3, 1}, + &output_tensor)); } - outputs->push_back(TRT_TensorOrWeights(output_tensor)); + outputs->push_back(TRT_TensorOrWeights( + const_cast(output_tensor))); return tensorflow::Status::OK(); } @@ -1937,10 +1996,9 @@ tensorflow::Status ConvertUnary(Converter& ctx, #endif // TODO(jie): check type - const nvinfer1::ITensor* tensor; - TFTRT_RETURN_ERROR_IF_FALSE( - PrepareTensorForShape(ctx, inputs.at(0), inputs.at(0).shape(), &tensor), - node_def.name()); + const nvinfer1::ITensor* tensor = nullptr; + TF_RETURN_IF_ERROR( + ctx.PrepareTensorForShape(inputs.at(0), inputs.at(0).shape(), &tensor)); nvinfer1::IUnaryLayer* layer; if (node_def.op() == "Rsqrt") { @@ -1960,7 +2018,8 @@ tensorflow::Status ConvertUnary(Converter& ctx, TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); nvinfer1::ITensor* output_tensor = layer->getOutput(0); - outputs->push_back(TRT_TensorOrWeights(output_tensor)); + outputs->push_back(TRT_TensorOrWeights( + const_cast(output_tensor))); return tensorflow::Status::OK(); } @@ -2030,16 +2089,17 @@ tensorflow::Status ConvertReducePool( permutation_order[1] = permuted_index; // Apply permutation before extracting dimension for pool_kernel - tensor = ctx.TransposeTensor(const_cast(tensor), - permutation_order); - TFTRT_RETURN_ERROR_IF_NULLPTR(tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(tensor), + permutation_order, + &tensor)); } // Apply permutation before extracting dimension for pool_kernel pool_kernel.d[0] = (idx_set.count(2) || permuted_index == 2) ? dims.d[1] : 1; pool_kernel.d[1] = (idx_set.count(3) || permuted_index == 3) ? dims.d[2] : 1; - nvinfer1::ITensor* output_tensor; + const nvinfer1::ITensor* output_tensor = nullptr; if (node_def.op() == "Mean") { nvinfer1::IPoolingLayer* layer = @@ -2053,11 +2113,13 @@ tensorflow::Status ConvertReducePool( } if (permuted_index != -1) { // Apply permutation before extracting dimension for pool_kernel - output_tensor = ctx.TransposeTensor( - const_cast(output_tensor), permutation_order); - TFTRT_RETURN_ERROR_IF_NULLPTR(output_tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(output_tensor), + permutation_order, + &output_tensor)); } - outputs->push_back(TRT_TensorOrWeights(output_tensor)); + outputs->push_back(TRT_TensorOrWeights( + const_cast(output_tensor))); return tensorflow::Status::OK(); } #elif NV_TENSORRT_MAJOR > 3 @@ -2205,9 +2267,10 @@ tensorflow::Status ConvertPad(Converter& ctx, std::vector permuted_pad_index(pad_index); if (pad_index[0] == 1) { legit_pad = false; - tensor = ctx.TransposeTensor(const_cast(tensor), - {0, 3, 2, 1}); - TFTRT_RETURN_ERROR_IF_NULLPTR(tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(tensor), + {0, 3, 2, 1}, + &tensor)); permuted_pad_index[0] = 3; } @@ -2225,15 +2288,17 @@ tensorflow::Status ConvertPad(Converter& ctx, nvinfer1::IPaddingLayer* layer = ctx.network()->addPadding( *const_cast(tensor), pre_padding, post_padding); TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); - nvinfer1::ITensor* output_tensor = layer->getOutput(0); + const nvinfer1::ITensor* output_tensor = layer->getOutput(0); if (!legit_pad) { - output_tensor = ctx.TransposeTensor( - const_cast(output_tensor), {0, 3, 2, 1}); - TFTRT_RETURN_ERROR_IF_NULLPTR(output_tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(output_tensor), + {0, 3, 2, 1}, + &output_tensor)); } - outputs->push_back(TRT_TensorOrWeights(output_tensor)); + outputs->push_back(TRT_TensorOrWeights( + const_cast(output_tensor))); return tensorflow::Status::OK(); } @@ -2314,9 +2379,10 @@ tensorflow::Status ConvertConcat(Converter& ctx, #if NV_TENSORRT_MAJOR == 3 // TRT3 does concatenation only on channel! if (index != 1) { - tensor_i = ctx.TransposeTensor(const_cast(tensor_i), - permutation_order); - TFTRT_RETURN_ERROR_IF_NULLPTR(tensor_i, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + const_cast(tensor_i), + permutation_order, + &tensor_i)); } #endif inputs_vec.push_back(tensor_i); @@ -2334,8 +2400,10 @@ tensorflow::Status ConvertConcat(Converter& ctx, #if NV_TENSORRT_MAJOR == 3 if (index != 1) { - output_tensor = ctx.TransposeTensor(output_tensor, permutation_order); - TFTRT_RETURN_ERROR_IF_NULLPTR(output_tensor, node_def.name()); + TF_RETURN_IF_ERROR(ctx.TransposeTensor( + output_tensor, + permutation_order, + &output_tensor)); } #endif outputs->push_back(TRT_TensorOrWeights(output_tensor)); @@ -2489,21 +2557,20 @@ tensorflow::Status ConvertMatMulHelper( while (input_dim.nbDims != 3) { input_dim.d[input_dim.nbDims++] = 1; } - TFTRT_RETURN_ERROR_IF_FALSE( - PrepareTensorForShape(ctx, tensor_input, input_dim, &tensor), node_name); + TF_RETURN_IF_ERROR( + ctx.PrepareTensorForShape(tensor_input, input_dim, &tensor)); nvinfer1::IFullyConnectedLayer* layer = ctx.network()->addFullyConnected( *const_cast(tensor), noutput, weights, biases); TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_name); output_tensor = layer->getOutput(0); - const nvinfer1::ITensor* temp_tensor; + const nvinfer1::ITensor* temp_tensor = nullptr; auto output_dim = output_tensor->getDimensions(); output_dim.nbDims = 1; - TFTRT_RETURN_ERROR_IF_FALSE( - PrepareTensorForShape(ctx, TRT_TensorOrWeights(output_tensor), output_dim, - &temp_tensor), - node_name); + TF_RETURN_IF_ERROR( + ctx.PrepareTensorForShape(TRT_TensorOrWeights(output_tensor), output_dim, + &temp_tensor)); output_tensor = const_cast(temp_tensor); outputs->push_back(TRT_TensorOrWeights(output_tensor)); return tensorflow::Status::OK(); @@ -2604,13 +2671,10 @@ tensorflow::Status ConvertBatchMatMul( dims_r.nbDims--; } } - - TFTRT_RETURN_ERROR_IF_FALSE( - PrepareTensorForShape(ctx, inputs.at(0), dims_l, &tensor_l), - node_def.name()); - TFTRT_RETURN_ERROR_IF_FALSE( - PrepareTensorForShape(ctx, inputs.at(1), dims_r, &tensor_r), - node_def.name()); + TF_RETURN_IF_ERROR( + ctx.PrepareTensorForShape(inputs.at(0), dims_l, &tensor_l)); + TF_RETURN_IF_ERROR( + ctx.PrepareTensorForShape(inputs.at(1), dims_r, &tensor_r)); nvinfer1::IMatrixMultiplyLayer* layer = ctx.network()->addMatrixMultiply( *const_cast(tensor_l), transpose_a, @@ -2782,7 +2846,8 @@ tensorflow::Status ConvertGraphDefToEngine( // Build the network VLOG(1) << "Starting engine conversion "; - Converter converter(trt_network.get(), ws.get(), precision_mode == FP16MODE); + Converter converter(trt_network.get(), ws.get(), precision_mode == FP16MODE, + max_batch_size); std::vector> output_tensors; // Graph nodes are already topologically sorted during construction for (const auto& node_def : gdef.node()) { diff --git a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py index 81dad5e1a4..61d95bb242 100644 --- a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py +++ b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py @@ -46,8 +46,9 @@ class SimpleReshapeTest(trt_test.TfTrtIntegrationTestBase): dtype=dtype, shape=[None] + input_dims[1:], name=input_name) with g.device("/GPU:0"): reshape = array_ops.reshape(inp, [-1, 24*24*2]) - print('RESHAPE SHAPE', reshape.get_shape().as_list()) + # Add identities to ensure we have at least min_segment_size=3 nodes identity = array_ops.identity(reshape, "identity") + identity = array_ops.identity(identity, "identity2") array_ops.identity(identity, name=output_name) return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), @@ -60,6 +61,150 @@ class SimpleReshapeTest(trt_test.TfTrtIntegrationTestBase): """Return the expected engines to build.""" return ["my_trt_op_0"] +class ReshapeToScalarTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + dtype = dtypes.float32 + input_name = "input" + input_dims = [1] + output_name = "output" + g = ops.Graph() + with g.as_default(): + inp = array_ops.placeholder( + dtype=dtype, shape=input_dims, name=input_name) + with g.device("/GPU:0"): + reshape = array_ops.reshape(inp, []) + # Add identities to ensure we have at least min_segment_size=3 nodes + identity = array_ops.identity(reshape, "identity") + identity = array_ops.identity(identity, "identity2") + array_ops.identity(identity, 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=[()]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return [] + + def ShouldRunTest(self, run_params): + """Whether to run the test.""" + # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not + # a calib graph. Doesn't seem to contain any calibration nodes."" + return (not trt_test.IsQuantizationMode(run_params.precision_mode) and + not run_params.dynamic_engine) + +class ReshapeBatchDimensionTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + reshape = array_ops.reshape(inp, [2, 50, 24, 24, 2]) + # Add identities to ensure we have at least min_segment_size=3 nodes + identity = array_ops.identity(reshape, "identity") + identity = array_ops.identity(identity, "identity2") + array_ops.identity(identity, 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=[(2, 50, 24, 24, 2)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return [] + + def ShouldRunTest(self, run_params): + """Whether to run the test.""" + # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not + # a calib graph. Doesn't seem to contain any calibration nodes."" + return (not trt_test.IsQuantizationMode(run_params.precision_mode) and + not run_params.dynamic_engine) + +class ReshapeBatchDimensionTest2(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + reshape = array_ops.reshape(inp, [-1, 50, 24, 24, 2]) + # Add identities to ensure we have at least min_segment_size=3 nodes + identity = array_ops.identity(reshape, "identity") + identity = array_ops.identity(identity, "identity2") + array_ops.identity(identity, 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=[(2, 50, 24, 24, 2)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return [] + + def ShouldRunTest(self, run_params): + """Whether to run the test.""" + # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not + # a calib graph. Doesn't seem to contain any calibration nodes."" + return (not trt_test.IsQuantizationMode(run_params.precision_mode) and + not run_params.dynamic_engine) + +class ReshapeBatchDimensionTest3(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + reshape = array_ops.reshape(inp, [2, 50, -1, 24, 2]) + # Add identities to ensure we have at least min_segment_size=3 nodes + identity = array_ops.identity(reshape, "identity") + identity = array_ops.identity(identity, "identity2") + array_ops.identity(identity, 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=[(2, 50, 24, 24, 2)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return [] + + def ShouldRunTest(self, run_params): + """Whether to run the test.""" + # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not + # a calib graph. Doesn't seem to contain any calibration nodes."" + return (not trt_test.IsQuantizationMode(run_params.precision_mode) and + not run_params.dynamic_engine) + class ReshapeInverseTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): @@ -149,6 +294,41 @@ class SimpleTransposeTest(trt_test.TfTrtIntegrationTestBase): """Return the expected engines to build.""" return ["my_trt_op_0"] +class TransposeBatchDimensionTest(trt_test.TfTrtIntegrationTestBase): + + def GetParams(self): + """Create a graph containing single segment.""" + dtype = dtypes.float32 + input_name = "input" + input_dims = [100, 24, 24, 2] + 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"): + # to NCHW + transpose = array_ops.transpose(inp, [2, 1, 0, 3]) + identity = array_ops.identity(transpose, "identity") + array_ops.identity(identity, 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=[(24, 24, 100, 2)]) + + def ExpectedEnginesToBuild(self, run_params): + """Return the expected engines to build.""" + return [] + + def ShouldRunTest(self, run_params): + """Whether to run the test.""" + # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not + # a calib graph. Doesn't seem to contain any calibration nodes."" + return (not trt_test.IsQuantizationMode(run_params.precision_mode) and + not run_params.dynamic_engine) + class TransposeInverseTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): -- GitLab From 3930c0c521c01d30599430f7178c6e9d98c7283b Mon Sep 17 00:00:00 2001 From: Deepak B Date: Fri, 12 Oct 2018 15:55:48 -0700 Subject: [PATCH 0103/1825] Create bug_template.md --- .github/ISSUE_TEMPLATE/bug_template.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_template.md diff --git a/.github/ISSUE_TEMPLATE/bug_template.md b/.github/ISSUE_TEMPLATE/bug_template.md new file mode 100644 index 0000000000..c8ab3e2a2c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_template.md @@ -0,0 +1,24 @@ +Please make sure that this is a bug. As per our GitHub Policy [link] we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. + +Please fill the following Bug_Template: +### System information +- Have I written custom code (as opposed to using a stock example script provided in TensorFlow): +- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): +- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: +- TensorFlow installed from (source or binary): +- TensorFlow version (use command below): +- Python version: +- Bazel version (if compiling from source): +- GCC/Compiler version (if compiling from source): +- CUDA/cuDNN version: +- GPU model and memory: +- Docker Image: + +You can use [this script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) to collect some of the information asked above. + +### Describe the current behavior + +### Describe the expected behavior + +### Code to reproduce the issue +Provide a reproducible test case that is the bare minimum necessary to generate the problem. -- GitLab From fbce5de12c2ca254febaf05b6913a1e8c6da7cbd Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Fri, 12 Oct 2018 16:14:39 -0700 Subject: [PATCH 0104/1825] Use fs->FileExists to update the searching status for the NOT_FOUNT case --- .../kernels/data/matching_files_dataset_op.cc | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index f12e376ddc..f6c9860f03 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -260,14 +260,25 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { Status s = fs->GetChildren(current_dir, &children); std::cout << "Children Num: " << children.size() << "; Status: " << s.ToString() - << "; Current dir: " << current_dir << std::endl; + << "; Current dir: " << current_dir + << "; FileExist status: " + << fs->FileExists(current_dir).ToString() << std::endl; ret.Update(s); - // If GetChildren() fails, continue the next search. - if (ret.code() == error::NOT_FOUND) { - continue; - } else if (!ret.ok()) { - return ret; + // When the children is empty, 1) return the non-ok status immediately + // if it is not NOT_FOUND; 2) continue the search if the status is ok + // or NOT_FOUND; + if (children.empty()) { + if (ret.code() != error::NOT_FOUND || !ret.ok()) { + return ret; + } else { + // On some platforms, fs.GetChildren() return the OK status even + // if the path isn't found. fs->FileExists() is used to make + // different platforms return the same status when searching a + // non-existing path. + ret.Update(fs->FileExists(current_dir)); + continue; + } } // children_dir_status holds is_dir status for children. It can have -- GitLab From 79b0e3a8229530aaeea489676af8ab170debcaf7 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 13 Oct 2018 16:23:27 +0000 Subject: [PATCH 0105/1825] Update softmax shape validation to not use tensor graph Signed-off-by: Yong Tang --- tensorflow/python/ops/nn_ops.py | 34 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index 70601dfaba..2064d77ae1 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -25,13 +25,13 @@ import numpy as np from tensorflow.python.compat import compat from tensorflow.python.eager import context from tensorflow.python.framework import dtypes +from tensorflow.python.framework import errors_impl from tensorflow.python.framework import graph_util from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import tensor_util from tensorflow.python.ops import array_ops from tensorflow.python.ops import check_ops -from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import gen_nn_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import random_ops @@ -1678,27 +1678,29 @@ def _softmax(logits, compute_op, dim=-1, name=None): if is_last_dim: return compute_op(logits, name=name) + dim_val = tensor_util.constant_value(dim) if isinstance(dim, ops.Tensor) else dim + if dim_val is not None and (dim_val < -shape.ndims or dim_val >= shape.ndims): + raise errors_impl.InvalidArgumentError( + None, None, + "Dimension (%d) must be in the range [%d, %d) where %d is the number of dimensions in the input." % (dim_val, -shape.ndims, shape.ndims, shape.ndims)) # If dim is not the last dimension, we have to do a transpose so that we can # still perform softmax on its last dimension. - is_valid_dim = control_flow_ops.Assert(math_ops.logical_and( - math_ops.greater_equal(dim, -shape.ndims), - math_ops.less(dim, shape.ndims)), [dim]) - with ops.control_dependencies([is_valid_dim]): - # Swap logits' dimension of dim and its last dimension. - input_rank = array_ops.rank(logits) - dim_axis = dim % shape.ndims - logits = _swap_axis(logits, dim_axis, math_ops.subtract(input_rank, 1)) - # Do the actual softmax on its last dimension. - output = compute_op(logits) + # Swap logits' dimension of dim and its last dimension. + input_rank = array_ops.rank(logits) + dim_axis = dim % shape.ndims + logits = _swap_axis(logits, dim_axis, math_ops.subtract(input_rank, 1)) - output = _swap_axis( - output, dim_axis, math_ops.subtract(input_rank, 1), name=name) + # Do the actual softmax on its last dimension. + output = compute_op(logits) - # Make shape inference work since transpose may erase its static shape. - output.set_shape(shape) + output = _swap_axis( + output, dim_axis, math_ops.subtract(input_rank, 1), name=name) - return output + # Make shape inference work since transpose may erase its static shape. + output.set_shape(shape) + + return output @tf_export("nn.softmax", "math.softmax") -- GitLab From d5ab586494a992733b8873531237dd2d200afeed Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 13 Oct 2018 16:30:00 +0000 Subject: [PATCH 0106/1825] Pylint fix for string too long Signed-off-by: Yong Tang --- tensorflow/python/ops/nn_ops.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/nn_ops.py b/tensorflow/python/ops/nn_ops.py index 2064d77ae1..953aa42e02 100644 --- a/tensorflow/python/ops/nn_ops.py +++ b/tensorflow/python/ops/nn_ops.py @@ -1678,11 +1678,16 @@ def _softmax(logits, compute_op, dim=-1, name=None): if is_last_dim: return compute_op(logits, name=name) - dim_val = tensor_util.constant_value(dim) if isinstance(dim, ops.Tensor) else dim + dim_val = dim + if isinstance(dim, ops.Tensor): + dim_val = tensor_util.constant_value(dim) if dim_val is not None and (dim_val < -shape.ndims or dim_val >= shape.ndims): raise errors_impl.InvalidArgumentError( None, None, - "Dimension (%d) must be in the range [%d, %d) where %d is the number of dimensions in the input." % (dim_val, -shape.ndims, shape.ndims, shape.ndims)) + "Dimension (%d) must be in the range [%d, %d) where %d is the number of" + " dimensions in the input." % + (dim_val, -shape.ndims, shape.ndims, shape.ndims)) + # If dim is not the last dimension, we have to do a transpose so that we can # still perform softmax on its last dimension. -- GitLab From fd9d2b0cb61b61ef8cf7e5b8459f20bfdfea127c Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 13 Oct 2018 17:19:33 +0000 Subject: [PATCH 0107/1825] Fix tests Signed-off-by: Yong Tang --- tensorflow/python/ops/nn_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/nn_test.py b/tensorflow/python/ops/nn_test.py index 2fabb2e966..6499d34652 100644 --- a/tensorflow/python/ops/nn_test.py +++ b/tensorflow/python/ops/nn_test.py @@ -95,7 +95,7 @@ class SoftmaxTest(test_lib.TestCase, parameterized.TestCase): arr = np.linspace(0., 1, 12).reshape(3, 4) x_neg_axis = nn_ops.softmax(arr, axis=-2) y_pos_axis = nn_ops.softmax(arr, axis=0) - z_gt_axis = nn_ops.softmax(arr, axis=4) + z_gt_axis = nn_ops.softmax(arr, axis=0) x_neg_axis_tf = self.evaluate(x_neg_axis) y_pos_axis_tf = self.evaluate(y_pos_axis) z_gt_axis_tf = self.evaluate(z_gt_axis) @@ -180,7 +180,7 @@ class LogSoftmaxTest(test_lib.TestCase, parameterized.TestCase): arr = np.linspace(0., 1, 12).reshape(3, 4) x_neg_axis = nn_ops.log_softmax(arr, axis=-2) y_pos_axis = nn_ops.log_softmax(arr, axis=0) - z_gt_axis = nn_ops.log_softmax(arr, axis=4) + z_gt_axis = nn_ops.log_softmax(arr, axis=0) x_neg_axis_tf = self.evaluate(x_neg_axis) y_pos_axis_tf = self.evaluate(y_pos_axis) z_gt_axis_tf = self.evaluate(z_gt_axis) -- GitLab From 7b3ffd2cd8048c15ed9365d4e205f964c646a3ce Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 13 Oct 2018 11:57:01 -0700 Subject: [PATCH 0108/1825] Fix MacOSX build failure While building tensorflow on MaxOSX (`Apple LLVM version 10.0.0 (clang-1000.11.45.2)`, `Python 2.7.10`) the following build failure surfaced: ``` In file included from tensorflow/python/eager/pywrap_tfe_src.cc:18: In file included from ./tensorflow/python/eager/pywrap_tfe.h:22: In file included from ./tensorflow/core/lib/core/status.h:23: In file included from bazel-out/host/genfiles/tensorflow/core/lib/core/error_codes.pb.h:9: In file included from external/protobuf_archive/src/google/protobuf/stubs/common.h:39: In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/iostream:38: In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/ios:216: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__locale:518:15: error: C++ requires a type specifier for all declarations char_type toupper(char_type __c) const ^ bazel-out/host/genfiles/external/local_config_python/python_include/pyport.h:731:29: note: expanded from macro 'toupper' ``` This fix fixes the build failure. Signed-off-by: Yong Tang --- tensorflow/python/eager/pywrap_tfe.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tensorflow/python/eager/pywrap_tfe.h b/tensorflow/python/eager/pywrap_tfe.h index decd635b58..1efdacce12 100755 --- a/tensorflow/python/eager/pywrap_tfe.h +++ b/tensorflow/python/eager/pywrap_tfe.h @@ -16,6 +16,11 @@ limitations under the License. #ifndef TENSORFLOW_PYTHON_EAGER_PYWRAP_TFE_H_ #define TENSORFLOW_PYTHON_EAGER_PYWRAP_TFE_H_ +// Place `` before to avoid build failure in macOS. +#include + +// The empty line above is on purpose as otherwise clang-format will +// automatically move before . #include #include "tensorflow/c/eager/c_api.h" -- GitLab From 5b85449b8d5818358ef1223814fff3f6a70f30cc Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Sat, 13 Oct 2018 23:15:00 -0700 Subject: [PATCH 0109/1825] Return NotFound if the input patterns result in empty match --- .../kernels/data/matching_files_dataset_op.cc | 38 +++++++++++-------- .../matching_files_dataset_op_test.py | 2 +- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index f6c9860f03..08953ee390 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -130,6 +130,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { std::move(current_path.first); out_tensors->emplace_back(std::move(filepath_tensor)); *end_of_sequence = false; + hasMatch_ = true; return Status::OK(); } @@ -171,7 +172,11 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { } *end_of_sequence = true; - return Status::OK(); + if (hasMatch_) { + return Status::OK(); + } else { + return errors::NotFound("Don't find any matched files"); + } } protected: @@ -182,6 +187,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("current_pattern"), current_pattern_)); + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name("hasMatch"), hasMatch_)); if (!filepath_queue_.empty()) { TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("queue_size"), @@ -212,6 +219,10 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("current_pattern"), ¤t_pattern_)); + int64 hasMatch; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name("hasMatch"), &hasMatch)); + hasMatch_ = static_cast(hasMatch); if (reader->Contains(full_name("queue_size"))) { int64 queue_size; @@ -224,7 +235,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { full_name(strings::StrCat("path_", i)), &path)); TF_RETURN_IF_ERROR(reader->ReadScalar( full_name(strings::StrCat("path_status_", i)), &path_status)); - filepath_queue_.push(PathStatus(path, path_status)); + filepath_queue_.push( + PathStatus(path, static_cast(path_status))); } } @@ -265,20 +277,13 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { << fs->FileExists(current_dir).ToString() << std::endl; ret.Update(s); - // When the children is empty, 1) return the non-ok status immediately - // if it is not NOT_FOUND; 2) continue the search if the status is ok - // or NOT_FOUND; - if (children.empty()) { - if (ret.code() != error::NOT_FOUND || !ret.ok()) { - return ret; - } else { - // On some platforms, fs.GetChildren() return the OK status even - // if the path isn't found. fs->FileExists() is used to make - // different platforms return the same status when searching a - // non-existing path. - ret.Update(fs->FileExists(current_dir)); - continue; - } + // Handle the error cases: 1) continue the search if the status is ok + // or NOT_FOUND; 2) return the non-ok status immediately if it is not + // NOT_FOUND. + if (ret.code() == error::NOT_FOUND) { + continue; + } else if (!ret.ok()) { + return ret; } // children_dir_status holds is_dir status for children. It can have @@ -345,6 +350,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { filepath_queue_ GUARDED_BY(mu_); size_t current_pattern_index_ GUARDED_BY(mu_) = 0; string current_pattern_ GUARDED_BY(mu_); + bool hasMatch_ GUARDED_BY(mu_) = false; }; const std::vector patterns_; diff --git a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py index 2a60b653d2..05f8958d2f 100644 --- a/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py +++ b/tensorflow/python/data/kernel_tests/matching_files_dataset_op_test.py @@ -61,7 +61,7 @@ class MatchingFilesDatasetTest(test_base.DatasetTestBase): dataset = MatchingFilesDataset(os.path.join(self.tmp_dir, '*')) with self.cached_session() as sess: next_element = dataset.make_one_shot_iterator().get_next() - with self.assertRaises(errors.OutOfRangeError): + with self.assertRaises(errors.NotFoundError): sess.run(next_element) def testSimpleDirectory(self): -- GitLab From 8b8c3accfb6f536db84860a2302e19c9d632bed4 Mon Sep 17 00:00:00 2001 From: Seunghoon Park Date: Sun, 14 Oct 2018 10:19:29 -0700 Subject: [PATCH 0110/1825] fix non_max_suppression_with_overlaps() function call - add a simple unit test case --- tensorflow/python/ops/image_ops_impl.py | 2 +- tensorflow/python/ops/image_ops_test.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/ops/image_ops_impl.py b/tensorflow/python/ops/image_ops_impl.py index 5a8e5d8399..7f41814b75 100644 --- a/tensorflow/python/ops/image_ops_impl.py +++ b/tensorflow/python/ops/image_ops_impl.py @@ -2210,7 +2210,7 @@ def non_max_suppression_with_overlaps(overlaps, overlap_threshold = ops.convert_to_tensor( overlap_threshold, name='overlap_threshold') # pylint: disable=protected-access - return gen_image_ops._non_max_suppression_v3( + return gen_image_ops.non_max_suppression_with_overlaps( overlaps, scores, max_output_size, overlap_threshold, score_threshold) # pylint: enable=protected-access diff --git a/tensorflow/python/ops/image_ops_test.py b/tensorflow/python/ops/image_ops_test.py index 81c2cc526e..438ecc05e0 100644 --- a/tensorflow/python/ops/image_ops_test.py +++ b/tensorflow/python/ops/image_ops_test.py @@ -3771,6 +3771,29 @@ class NonMaxSuppressionPaddedTest(test_util.TensorFlowTestCase): self.assertAllClose(selected_indices.eval(), [0, 2, 4]) self.assertEqual(num_valid.eval(), 3) +class NonMaxSuppressionWithOverlapsTest(test_util.TensorFlowTestCase): + + def testSelectOneFromThree(self): + overlaps_np = [ + [1.0, 0.7, 0.2], + [0.7, 1.0, 0.0], + [0.2, 0.0, 1.0], + ] + scores_np = [0.7, 0.9, 0.1] + max_ouput_size_np = 3 + + overlaps = constant_op.constant(overlaps_np) + scores = constant_op.constant(scores_np) + max_output_size = constant_op.constant(max_ouput_size_np) + overlap_threshold = 0.6 + score_threshold = 0.4 + + selected_indices = image_ops.non_max_suppression_with_overlaps( + overlaps, scores, max_output_size, overlap_threshold, score_threshold) + + with self.cached_session(): + self.assertAllClose(selected_indices.eval(), [1]) + class VerifyCompatibleImageShapesTest(test_util.TensorFlowTestCase): """Tests utility function used by ssim() and psnr().""" -- GitLab From a5a02c6bc9821d1e7e278628c0b7e02c282ba0c0 Mon Sep 17 00:00:00 2001 From: drpngx Date: Sun, 14 Oct 2018 14:49:32 -0700 Subject: [PATCH 0111/1825] Fix typo. --- tensorflow/core/api_def/excluded_ops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/core/api_def/excluded_ops.cc b/tensorflow/core/api_def/excluded_ops.cc index 3db69f6af8..02026e94ab 100644 --- a/tensorflow/core/api_def/excluded_ops.cc +++ b/tensorflow/core/api_def/excluded_ops.cc @@ -23,7 +23,7 @@ const std::unordered_set* GetExcludedOps() { {"BigQueryReader", "GenerateBigQueryReaderPartitions", "GcsConfigureBlockCache", "GcsConfigureCredentials", #ifdef INTEL_MKL - // QuatizedFusedOps for Intel CPU + // QuantizedFusedOps for Intel CPU "QuantizedConv2DAndRequantize", "QuantizedConv2DWithBias", "QuantizedConv2DWithBiasAndRequantize", "QuantizedConv2DAndRelu", "QuantizedConv2DAndReluAndRequantize", -- GitLab From 73b318f8e698cd61adb8e281ea3b95b886003617 Mon Sep 17 00:00:00 2001 From: Grzegorz Pawelczak Date: Mon, 15 Oct 2018 11:33:42 +0100 Subject: [PATCH 0112/1825] [XLA] Sink constants into the conditional computation in while loop --- .../service/while_loop_constant_sinking.cc | 45 +++++-- .../xla/service/while_loop_constant_sinking.h | 9 +- .../while_loop_constant_sinking_test.cc | 127 ++++++++++++++++++ tensorflow/compiler/xla/service/while_util.cc | 13 ++ tensorflow/compiler/xla/service/while_util.h | 7 + 5 files changed, 182 insertions(+), 19 deletions(-) diff --git a/tensorflow/compiler/xla/service/while_loop_constant_sinking.cc b/tensorflow/compiler/xla/service/while_loop_constant_sinking.cc index 067cfcc17d..49c05e9cf7 100644 --- a/tensorflow/compiler/xla/service/while_loop_constant_sinking.cc +++ b/tensorflow/compiler/xla/service/while_loop_constant_sinking.cc @@ -46,8 +46,9 @@ static Status ReplaceUsesWhileKeepingLoopInvariance( return Status::OK(); } -StatusOr WhileLoopConstantSinking::TrySinkingConstantsIntoWhileBody( +StatusOr WhileLoopConstantSinking::TrySinkingConstantsIntoWhileLoop( HloInstruction* while_instr) { + HloComputation* while_cond = while_instr->while_condition(); HloComputation* while_body = while_instr->while_body(); const HloInstruction& init_value = *while_instr->operand(0); @@ -57,24 +58,44 @@ StatusOr WhileLoopConstantSinking::TrySinkingConstantsIntoWhileBody( bool changed = false; - for (HloInstruction* invariant_gte : - WhileUtil::GetInvariantGTEsForWhileBody(*while_body)) { - int64 index = invariant_gte->tuple_index(); + auto invariant_conditional_gte_index_to_inst = + WhileUtil::GetGTEsMapForWhileConditional(*while_cond); + auto invariant_body_gtes = + WhileUtil::GetInvariantGTEsForWhileBody(*while_body); + + for (HloInstruction* invariant_body_gte : invariant_body_gtes) { + int64 index = invariant_body_gte->tuple_index(); const HloInstruction& invariant_value = *init_value.operand(index); - // Should have at least one user that's not while_body_root. - if (invariant_gte->user_count() <= 1) { - continue; - } + // Original value should be a constant + if (invariant_value.opcode() != HloOpcode::kConstant) continue; - if (invariant_value.opcode() == HloOpcode::kConstant) { + // Sink into the while_body + // Should have at least one user that's not while_body_root. + if (invariant_body_gte->user_count() > 1) { auto* constant_instr = while_body->AddInstruction(invariant_value.Clone(/*suffix=*/".sunk")); TF_RETURN_IF_ERROR(ReplaceUsesWhileKeepingLoopInvariance( - invariant_gte, constant_instr, while_body->root_instruction(), + invariant_body_gte, constant_instr, while_body->root_instruction(), index)); changed = true; } + + // Check if there is a corresponding GTE in while_conditional + auto it = invariant_conditional_gte_index_to_inst.find(index); + if (it == invariant_conditional_gte_index_to_inst.end()) { + continue; + } + + auto* invariant_cond_gte = it->second; + // Should have at least one user + if (invariant_cond_gte->user_count() > 0) { + auto* constant_instr = + while_cond->AddInstruction(invariant_value.Clone(/*suffix=*/".sunk")); + TF_RETURN_IF_ERROR( + invariant_cond_gte->ReplaceAllUsesWith(constant_instr)); + changed = true; + } } return changed; @@ -115,10 +136,8 @@ StatusOr WhileLoopConstantSinking::Run(HloModule* module) { } for (HloInstruction* while_instr : while_instrs) { - // We only sink into while loop bodies, but this can be extended to - // transform conditions as well. TF_ASSIGN_OR_RETURN(bool result, - TrySinkingConstantsIntoWhileBody(while_instr)); + TrySinkingConstantsIntoWhileLoop(while_instr)); changed |= result; } diff --git a/tensorflow/compiler/xla/service/while_loop_constant_sinking.h b/tensorflow/compiler/xla/service/while_loop_constant_sinking.h index 577bad6c70..2f8edb1219 100644 --- a/tensorflow/compiler/xla/service/while_loop_constant_sinking.h +++ b/tensorflow/compiler/xla/service/while_loop_constant_sinking.h @@ -23,8 +23,8 @@ limitations under the License. namespace xla { // Sinks while loop invariant values that happen to be constants into the while -// loop body. This is probably not a win in isolation but may unlock further -// optimizations like constant folding. +// loop body and conditional. This is probably not a win in isolation but may +// unlock further optimizations like constant folding. // // state = (..., const, ...) // while (pred(state)) { @@ -46,9 +46,6 @@ namespace xla { // tuple trivially loop invariant. WhileLoopSimplifier will later get rid of // `v`. // -// We only sink into while loop bodies, but this can be extended to transform -// conditions as well. -// // TODO(b/79121449): We should also sink broadcasts of constants. class WhileLoopConstantSinking : public HloModulePass { public: @@ -61,7 +58,7 @@ class WhileLoopConstantSinking : public HloModulePass { StatusOr Run(HloModule* module) override; private: - StatusOr TrySinkingConstantsIntoWhileBody(HloInstruction* while_instr); + StatusOr TrySinkingConstantsIntoWhileLoop(HloInstruction* while_instr); }; } // namespace xla diff --git a/tensorflow/compiler/xla/service/while_loop_constant_sinking_test.cc b/tensorflow/compiler/xla/service/while_loop_constant_sinking_test.cc index 0e7667de83..9a25be1022 100644 --- a/tensorflow/compiler/xla/service/while_loop_constant_sinking_test.cc +++ b/tensorflow/compiler/xla/service/while_loop_constant_sinking_test.cc @@ -242,5 +242,132 @@ ENTRY entry { } } } + +TEST_F(WhileLoopConstantSinkingTest, ConditionalSinkConstant) { + const char* const hlo_string = R"( +HloModule ModuleWithWhile + +body { + p_body = (f32[],f32[]) parameter(0) + p_body.0 = f32[] get-tuple-element((f32[],f32[]) p_body), index=0 + const = f32[] constant(1) + add = f32[] add(p_body.0, const) + p_body.1 = f32[] get-tuple-element((f32[],f32[]) p_body), index=1 + ROOT root = (f32[],f32[]) tuple(add, p_body.1) +} + +condition { + p_cond = (f32[],f32[]) parameter(0) + p_cond.0 = f32[] get-tuple-element((f32[],f32[]) p_cond), index=0 + p_cond.1 = f32[] get-tuple-element((f32[],f32[]) p_cond), index=1 + ROOT result = pred[] less-than(p_cond.0, p_cond.1) +} + +ENTRY entry { + const_0 = f32[] constant(0) + const_1 = f32[] constant(10) + while_init = (f32[],f32[]) tuple(const_0, const_1) + ROOT while = (f32[],f32[]) while(while_init), condition=condition, body=body +} +)"; + + TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr module, + ParseHloString(hlo_string)); + + TF_ASSERT_OK_AND_ASSIGN(bool changed, + WhileLoopConstantSinking{}.Run(module.get())); + ASSERT_TRUE(changed); + + auto* while_condition = module->GetComputationWithName("condition"); + EXPECT_THAT(while_condition->root_instruction(), op::Lt(_, op::Constant())); +} + +TEST_F(WhileLoopConstantSinkingTest, ConditionalTupleShapedConstants) { + const char* const hlo_string = R"( +HloModule ModuleWithWhile + +body { + p_b = (f32[],(f32[],f32[])) parameter(0) + p_b.0 = f32[] get-tuple-element((f32[],(f32[],f32[])) p_b), index=0 + p_b.1 = (f32[],f32[]) get-tuple-element((f32[],(f32[],f32[])) p_b), index=1 + p_b.1.0 = f32[] get-tuple-element((f32[],f32[]) p_b.1), index=0 + add = f32[] add(p_b.0, p_b.1.0) + ROOT root = (f32[],(f32[],f32[])) tuple(add, p_b.1) +} + +condition { + p_c = (f32[],(f32[],f32[])) parameter(0) + p_c.0 = f32[] get-tuple-element((f32[],f32[]) p_c), index=0 + p_c.1 = (f32[],f32[]) get-tuple-element((f32[],f32[]) p_c), index=1 + p_c.1.1 = f32[] get-tuple-element((f32[],f32[]) p_c.1), index=1 + ROOT result = pred[] less-than(p_c.0, p_c.1.1) +} + +ENTRY entry { + const_0 = f32[] constant(0) + const_1 = (f32[], f32[]) constant((f32[], f32[]) (1, 10)) + while_init = (f32[],(f32[],f32[])) tuple(const_0, const_1) + ROOT while = (f32[],(f32[],f32[])) while(while_init), condition=condition, body=body +} +)"; + + TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr module, + ParseHloString(hlo_string)); + + TF_ASSERT_OK_AND_ASSIGN(bool changed, + WhileLoopConstantSinking{}.Run(module.get())); + ASSERT_TRUE(changed); + + auto* while_condition = module->GetComputationWithName("condition"); + EXPECT_THAT(while_condition->root_instruction(), + op::Lt(_, op::GetTupleElement(op::Constant()))); +} + +TEST_F(WhileLoopConstantSinkingTest, ConditionalDontCreateDeadConstant) { + const char* const hlo_string = R"( +HloModule ModuleWithWhile + +body { + p_body = (f32[],f32[],f32[]) parameter(0) + p_body.0 = f32[] get-tuple-element((f32[],f32[],f32[]) p_body), index=0 + const = f32[] constant(1) + add = f32[] add(p_body.0, const) + p_body.1 = f32[] get-tuple-element((f32[],f32[],f32[]) p_body), index=1 + p_body.2 = f32[] get-tuple-element((f32[],f32[],f32[]) p_body), index=2 + ROOT root = (f32[],f32[],f32[]) tuple(add, p_body.1, p_body.2) +} + +condition { + p_cond = (f32[],f32[],f32[]) parameter(0) + p_cond.0 = f32[] get-tuple-element((f32[],f32[],f32[]) p_cond), index=0 + p_cond.1 = f32[] get-tuple-element((f32[],f32[],f32[]) p_cond), index=1 + p_cond.2 = f32[] get-tuple-element((f32[],f32[],f32[]) p_cond), index=2 + ROOT result = pred[] less-than(p_cond.0, p_cond.1) +} + +ENTRY entry { + const_0 = f32[] constant(0) + const_1 = f32[] constant(10) + const_2 = f32[] constant(12) + while_init = (f32[],f32[],f32[]) tuple(const_0, const_1, const_2) + ROOT while = (f32[],f32[],f32[]) while(while_init), condition=condition, body=body +} +)"; + + TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr module, + ParseHloString(hlo_string)); + + TF_ASSERT_OK_AND_ASSIGN(bool changed, + WhileLoopConstantSinking{}.Run(module.get())); + ASSERT_TRUE(changed); + + auto* while_condition = module->GetComputationWithName("condition"); + EXPECT_THAT(while_condition->root_instruction(), op::Lt(_, op::Constant())); + for (const HloInstruction* inst : while_condition->instructions()) { + if (inst->opcode() == HloOpcode::kConstant) { + EXPECT_GT(inst->user_count(), 0); + } + } +} } // namespace } // namespace xla diff --git a/tensorflow/compiler/xla/service/while_util.cc b/tensorflow/compiler/xla/service/while_util.cc index f90ac91f9d..153cd449d3 100644 --- a/tensorflow/compiler/xla/service/while_util.cc +++ b/tensorflow/compiler/xla/service/while_util.cc @@ -268,4 +268,17 @@ static Shape MakeLoopStateShape(const WhileUtil::LoopStateTy& init_values) { return result; } +/*static*/ std::map +WhileUtil::GetGTEsMapForWhileConditional( + const HloComputation& while_conditional) { + std::map result; + for (auto* inst : while_conditional.instructions()) { + if (inst->opcode() == HloOpcode::kGetTupleElement && + inst->operand(0) == while_conditional.parameter_instruction(0)) { + result[inst->tuple_index()] = inst; + } + } + return result; +} + } // namespace xla diff --git a/tensorflow/compiler/xla/service/while_util.h b/tensorflow/compiler/xla/service/while_util.h index b1c4486887..57ae0178b4 100644 --- a/tensorflow/compiler/xla/service/while_util.h +++ b/tensorflow/compiler/xla/service/while_util.h @@ -84,6 +84,13 @@ class WhileUtil { // Assumes `while_body` is the body computation of the while loop in question. static std::vector GetInvariantGTEsForWhileBody( const HloComputation& while_body); + + // Returns a map of index to GetTupleElement instructions in + // `while_conditional` that access elements in the parameter tuple. Assumes + // `while_conditional` is the conditional computation of the while loop in + // question. + static std::map GetGTEsMapForWhileConditional( + const HloComputation& while_conditional); }; } // namespace xla -- GitLab From e8bf4b49e372c49f51536731b7c9390fd541baf5 Mon Sep 17 00:00:00 2001 From: Grzegorz Pawelczak Date: Mon, 15 Oct 2018 13:14:28 +0100 Subject: [PATCH 0113/1825] Fix the pass name --- tensorflow/compiler/xla/service/while_loop_constant_sinking.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/while_loop_constant_sinking.h b/tensorflow/compiler/xla/service/while_loop_constant_sinking.h index 2f8edb1219..a866bc1264 100644 --- a/tensorflow/compiler/xla/service/while_loop_constant_sinking.h +++ b/tensorflow/compiler/xla/service/while_loop_constant_sinking.h @@ -52,7 +52,7 @@ class WhileLoopConstantSinking : public HloModulePass { ~WhileLoopConstantSinking() override = default; absl::string_view name() const override { - return "while-loop-invariant-code-motion"; + return "while-loop-constant-sinking"; } StatusOr Run(HloModule* module) override; -- GitLab From 32ef432c58bf9f0a91b4451832ca5b0ff83bcb02 Mon Sep 17 00:00:00 2001 From: Jonathan Wyatt Hoech Date: Mon, 2 Jul 2018 00:40:41 -0700 Subject: [PATCH 0114/1825] Adding matrix square root op. --- .../base_api/api_def_MatrixSquareRoot.pbtxt | 37 ++++++ .../python_api/api_def_MatrixSquareRoot.pbtxt | 9 ++ tensorflow/core/kernels/BUILD | 7 ++ .../core/kernels/matrix_square_root_op.cc | 57 +++++++++ tensorflow/core/ops/linalg_ops.cc | 6 + tensorflow/core/ops/ops.pbtxt | 23 ++++ tensorflow/go/op/wrappers.go | 40 ++++++ .../python/kernel_tests/linalg_grad_test.py | 15 +++ .../matrix_square_root_op_test.py | 115 ++++++++++++++++++ tensorflow/python/ops/linalg/linalg_impl.py | 1 + tensorflow/python/ops/linalg_grad.py | 59 +++++++++ .../api/golden/v2/tensorflow.linalg.pbtxt | 4 + .../tools/api/golden/v2/tensorflow.pbtxt | 4 + 13 files changed, 377 insertions(+) create mode 100644 tensorflow/core/api_def/base_api/api_def_MatrixSquareRoot.pbtxt create mode 100644 tensorflow/core/api_def/python_api/api_def_MatrixSquareRoot.pbtxt create mode 100644 tensorflow/core/kernels/matrix_square_root_op.cc create mode 100644 tensorflow/python/kernel_tests/matrix_square_root_op_test.py diff --git a/tensorflow/core/api_def/base_api/api_def_MatrixSquareRoot.pbtxt b/tensorflow/core/api_def/base_api/api_def_MatrixSquareRoot.pbtxt new file mode 100644 index 0000000000..a9f1e593cc --- /dev/null +++ b/tensorflow/core/api_def/base_api/api_def_MatrixSquareRoot.pbtxt @@ -0,0 +1,37 @@ +op { + graph_op_name: "MatrixSquareRoot" + in_arg { + name: "input" + description: < +class MatrixSquareRootOp : public LinearAlgebraOp { + public: + INHERIT_LINALG_TYPEDEFS(Scalar); + + explicit MatrixSquareRootOp(OpKernelConstruction* context) : Base(context) { + } + + void ComputeMatrix(OpKernelContext* context, const ConstMatrixMaps& inputs, + MatrixMaps* outputs) final { + const ConstMatrixMap& input = inputs[0]; + if (input.rows() == 0) return; + using Matrix = + Eigen::Matrix; + Matrix tmp = input; + outputs->at(0) = tmp.sqrt(); + } + + private: + TF_DISALLOW_COPY_AND_ASSIGN(MatrixSquareRootOp); +}; + +REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), float); +REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), double); +REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), complex64); +REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), complex128); +} diff --git a/tensorflow/core/ops/linalg_ops.cc b/tensorflow/core/ops/linalg_ops.cc index 1d4d51a25d..525b19e51e 100644 --- a/tensorflow/core/ops/linalg_ops.cc +++ b/tensorflow/core/ops/linalg_ops.cc @@ -323,6 +323,12 @@ REGISTER_OP("MatrixSolveLs") return MatrixSolveShapeFn(c, false /* square */); }); +REGISTER_OP("MatrixSquareRoot") + .Input("input: T") + .Output("output: T") + .Attr("T: {double, float, complex64, complex128}") + .SetShapeFn(BatchUnchangedSquareShapeFn); + REGISTER_OP("Qr") .Input("input: T") .Output("q: T") diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index d034ea27a1..ef063bb364 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -16070,6 +16070,29 @@ op { } } } +op { + name: "MatrixSquareRoot" + input_arg { + name: "matrix" + type_attr: "T" + } + output_arg { + name: "output" + type_attr: "T" + } + attr { + name: "T" + type: "type" + allowed_values { + list { + type: DT_DOUBLE + type: DT_FLOAT + type: DT_COMPLEX64 + type: DT_COMPLEX128 + } + } + } +} op { name: "MatrixTriangularSolve" input_arg { diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 2031e60773..33437df840 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -16646,6 +16646,46 @@ func MatrixSolveLs(scope *Scope, matrix tf.Output, rhs tf.Output, l2_regularizer return op.Output(0) } +// Computes the matrix square root of one or more square matrices: +// +// matmul(sqrtm(A), sqrtm(A)) = A +// +// The input matrix should be invertible. If the input matrix is real, +// it should have no eigenvalues which are real and negative +// (pairs of complex conjugate eigenvalues are allowed). +// +// The matrix square root is computed by first reducing the matrix to +// quasi-triangular form with the real Schur decomposition. The square root +// of the quasi-triangular matrix is then computed directly. Details of +// the algorithm can be found in: Nicholas J. Higham, "Computing real +// square roots of a real matrix", Linear Algebra Appl., 1987. +// +// The input is a tensor of shape `[..., M, M]` whose inner-most 2 dimensions +// form square matrices. The output is a tensor of the same shape as the input +// containing the matrix square root for all input submatrices `[..., :, :]`. +// +// Arguments: +// input: Shape is `[..., M, M]`. +// +// Returns Shape is `[..., M, M]`. +// +// @compatibility(scipy) +// Equivalent to scipy.linalg.sqrtm +// @end_compatibility +func MatrixSquareRoot(scope *Scope, input tf.Output) (output tf.Output) { + if scope.Err() != nil { + return + } + opspec := tf.OpSpec{ + Type: "MatrixSquareRoot", + Input: []tf.Input{ + input, + }, + } + op := scope.AddOperation(opspec) + return op.Output(0) +} + // MaxPool3DAttr is an optional argument to MaxPool3D. type MaxPool3DAttr func(optionalAttr) diff --git a/tensorflow/python/kernel_tests/linalg_grad_test.py b/tensorflow/python/kernel_tests/linalg_grad_test.py index e52f303fe0..86c027a691 100644 --- a/tensorflow/python/kernel_tests/linalg_grad_test.py +++ b/tensorflow/python/kernel_tests/linalg_grad_test.py @@ -66,6 +66,10 @@ def _GetMatrixUnaryFunctorGradientTest(functor_, dtype_, shape_, **kwargs_): low=-1.0, high=1.0, size=np.prod(shape_)).reshape(shape_).astype(dtype_) a = constant_op.constant(a_np) + if functor_.func_name == 'matrix_square_root': + # Square the input matrix to ensure that its matrix square root exists + a = math_ops.matmul(a, a) + a_np = a.eval() b = functor_(a, **kwargs_) # Optimal stepsize for central difference is O(epsilon^{1/3}). @@ -189,6 +193,17 @@ if __name__ == '__main__': lambda x: linalg_ops.log_matrix_determinant(x)[1], dtype, shape)) + # The numerical Jacobian is consistently invalid for these four shapes + # because the matrix square root of the perturbed input doesn't exist + if shape in {(2, 5, 5), (3, 5, 5), (3, 10, 10), (3, 2, 5, 5)}: + # Alternative shape that consistently produces a valid numerical Jacobian + shape = extra + (size + 1, size + 1) + name = '%s_%s' % (dtype.__name__, '_'.join(map(str, shape))) + _AddTest( + MatrixUnaryFunctorGradientTest, 'MatrixSquareRootGradient', name, + _GetMatrixUnaryFunctorGradientTest(linalg_ops.matrix_square_root, + dtype, shape)) + # Tests for gradients of matrix_solve_ls for dtype in np.float32, np.float64: for rows in 2, 5, 10: diff --git a/tensorflow/python/kernel_tests/matrix_square_root_op_test.py b/tensorflow/python/kernel_tests/matrix_square_root_op_test.py new file mode 100644 index 0000000000..3fda31ff73 --- /dev/null +++ b/tensorflow/python/kernel_tests/matrix_square_root_op_test.py @@ -0,0 +1,115 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for tensorflow.ops.math_ops.matrix_square_root.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.python.framework import constant_op +from tensorflow.python.ops import gen_linalg_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import random_ops +from tensorflow.python.platform import test + + +class SquareRootOpTest(test.TestCase): + + def _verifySquareRoot(self, matrix, np_type): + matrix = matrix.astype(np_type) + with self.test_session(use_gpu=True): + # Verify that matmul(sqrtm(A), sqrtm(A)) = A + sqrt = gen_linalg_ops.matrix_square_root(matrix) + square = math_ops.matmul(sqrt, sqrt) + self.assertShapeEqual(matrix, square) + self.assertAllClose(matrix, square, rtol=1e-4, atol=1e-3) + + def _verifySquareRootReal(self, x): + for np_type in [np.float32, np.float64]: + self._verifySquareRoot(x, np_type) + + def _verifySquareRootComplex(self, x): + for np_type in [np.complex64, np.complex128]: + self._verifySquareRoot(x, np_type) + + def _makeBatch(self, matrix1, matrix2): + matrix_batch = np.concatenate( + [np.expand_dims(matrix1, 0), + np.expand_dims(matrix2, 0)]) + matrix_batch = np.tile(matrix_batch, [2, 3, 1, 1]) + return matrix_batch + + def _testMatrices(self, matrix1, matrix2): + # Real + self._verifySquareRootReal(matrix1) + self._verifySquareRootReal(matrix2) + self._verifySquareRootReal(self._makeBatch(matrix1, matrix2)) + # Complex + matrix1 = matrix1.astype(np.complex64) + matrix2 = matrix2.astype(np.complex64) + matrix1 += 1j * matrix1 + matrix2 += 1j * matrix2 + self._verifySquareRootComplex(matrix1) + self._verifySquareRootComplex(matrix2) + self._verifySquareRootComplex(self._makeBatch(matrix1, matrix2)) + + def testSymmetricPositiveDefinite(self): + matrix1 = np.array([[2., 1.], [1., 2.]]) + matrix2 = np.array([[3., -1.], [-1., 3.]]) + self._testMatrices(matrix1, matrix2) + + def testAsymmetric(self): + matrix1 = np.array([[0., 4.], [-1., 5.]]) + matrix2 = np.array([[33., 24.], [48., 57.]]) + self._testMatrices(matrix1, matrix2) + + def testIdentityMatrix(self): + # 2x2 + identity = np.array([[1., 0], [0, 1.]]) + self._verifySquareRootReal(identity) + # 3x3 + identity = np.array([[1., 0, 0], [0, 1., 0], [0, 0, 1.]]) + self._verifySquareRootReal(identity) + + def testEmpty(self): + self._verifySquareRootReal(np.empty([0, 2, 2])) + self._verifySquareRootReal(np.empty([2, 0, 0])) + + def testWrongDimensions(self): + # The input to the square root should be at least a 2-dimensional tensor. + tensor = constant_op.constant([1., 2.]) + with self.assertRaises(ValueError): + gen_linalg_ops.matrix_square_root(tensor) + + def testNotSquare(self): + with self.test_session(): + with self.assertRaises(ValueError): + tensor = constant_op.constant([[1., 0., -1.], [-1., 1., 0.]]) + gen_linalg_ops.matrix_square_root(tensor).eval() + + def testConcurrentExecutesWithoutError(self): + with self.test_session(use_gpu=True) as sess: + matrix1 = random_ops.random_normal([5, 5], seed=42) + matrix2 = random_ops.random_normal([5, 5], seed=42) + sqrt1 = gen_linalg_ops.matrix_square_root(matrix1) + sqrt2 = gen_linalg_ops.matrix_square_root(matrix2) + all_ops = [sqrt1, sqrt2] + sqrt = sess.run(all_ops) + self.assertAllEqual(sqrt[0], sqrt[1]) + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/ops/linalg/linalg_impl.py b/tensorflow/python/ops/linalg/linalg_impl.py index 1e3d817980..08d50ce622 100644 --- a/tensorflow/python/ops/linalg/linalg_impl.py +++ b/tensorflow/python/ops/linalg/linalg_impl.py @@ -50,6 +50,7 @@ norm = linalg_ops.norm qr = linalg_ops.qr set_diag = array_ops.matrix_set_diag solve = linalg_ops.matrix_solve +sqrtm = linalg_ops.matrix_square_root svd = linalg_ops.svd tensordot = math_ops.tensordot trace = math_ops.trace diff --git a/tensorflow/python/ops/linalg_grad.py b/tensorflow/python/ops/linalg_grad.py index b6b98d5c86..cf03a84ff0 100644 --- a/tensorflow/python/ops/linalg_grad.py +++ b/tensorflow/python/ops/linalg_grad.py @@ -54,6 +54,65 @@ def _MatrixDeterminantGrad(op, grad): 0)) return multipliers * a_adj_inv +@ops.RegisterGradient("MatrixSquareRoot") +def _MatrixSquareRootGrad(op, grad): + """Gradient for MatrixSquareRoot.""" + + # Let A be an m x m square matrix (or batch of matrices) + # Let R = sqrtm(A) + # By definition, A = RR + # Take the differential: dA = d(RR) = RdR + dRR + # Solve the resulting Sylvester equation for dR + + # Used to find Kronecker products within the Sylvester equation + def _KroneckerProduct(mat1, mat2): + """Computes the Kronecker product of two matrices.""" + m1, n1 = mat1.get_shape().as_list() + mat1_rsh = array_ops.reshape(mat1, [m1, 1, n1, 1]) + m2, n2 = mat2.get_shape().as_list() + mat2_rsh = array_ops.reshape(mat2, [1, m2, 1, n2]) + return array_ops.reshape(mat1_rsh * mat2_rsh, [m1 * m2, n1 * n2]) + + sqrt_batch = op.outputs[0] # R + shape_batch = sqrt_batch.get_shape() + order = shape_batch.as_list()[-1] # m + identity = _linalg.eye(order, dtype=sqrt_batch.dtype) # m x m identity matrix + shape_matrix = [order, order] # Tensor may be a batch of matrices of shape m x m + + # Flatten batches containing R and dA + flat_sqrt = array_ops.reshape(sqrt_batch, [-1]) + flat_grad = array_ops.reshape(grad, [-1]) + + # Split flattened batches into m x m matrices + num_elements = flat_sqrt.get_shape().as_list()[-1] + num_splits = int(num_elements / (order * order)) + split_sqrt = array_ops.split(flat_sqrt, num_splits) + split_grad = array_ops.split(flat_grad, num_splits) + + matrix_gradients = [] # Raw gradients of all m x m matrices + for flat_sqrt_matrix, flat_grad_matrix in zip(split_sqrt, split_grad): + sqrt_matrix = array_ops.reshape(flat_sqrt_matrix, shape_matrix) # m x m matrix R + grad_matrix = array_ops.reshape(flat_grad_matrix, shape_matrix) # m x m matrix dA + + # The transpose of R is taken in the k1 term instead of k2 in + # order to prevent redundant transposition of R (i.e. (R')' = R) + sqrt_matrix_transpose = array_ops.matrix_transpose(sqrt_matrix) + k1 = _KroneckerProduct(identity, sqrt_matrix_transpose) + k2 = _KroneckerProduct(sqrt_matrix, identity) + + # Solve for vec(dR) by vectorizing dA + inv_ksum = _linalg.inv(math_ops.add(k1, k2)) + vec_da = array_ops.reshape(array_ops.matrix_transpose(grad_matrix), [-1]) + vec_dsqrt = _linalg.einsum('ij,j->i', inv_ksum, vec_da) # Matrix vector product + + # Solve for dR by inverse vectorizing vec(dR) + dsqrt_transpose = array_ops.reshape(vec_dsqrt, shape_matrix) + dsqrt = array_ops.matrix_transpose(dsqrt_transpose) + matrix_gradients.append(dsqrt) + + # Reshape raw gradients to the original input shape + return array_ops.reshape(array_ops.stack(matrix_gradients), shape_batch) + @ops.RegisterGradient("LogMatrixDeterminant") def _LogMatrixDeterminantGrad(op, _, grad_b): diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt index 6ac95d96da..801bf76b0c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt @@ -156,6 +156,10 @@ tf_module { name: "solve" argspec: "args=[\'matrix\', \'rhs\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " } + member_method { + name: "sqrtm" + argspec: "args=[\'matrix\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } member_method { name: "svd" argspec: "args=[\'tensor\', \'full_matrices\', \'compute_uv\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'True\', \'None\'], " diff --git a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt index 37e8e654b7..bb21b0ad8f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt @@ -1168,6 +1168,10 @@ tf_module { name: "matrix_solve" argspec: "args=[\'matrix\', \'rhs\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " } + member_method { + name: "matrix_square_root" + argspec: "args=[\'matrix\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } member_method { name: "matrix_triangular_solve" argspec: "args=[\'matrix\', \'rhs\', \'lower\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'True\', \'False\', \'None\'], " -- GitLab From e29eba62883f7ac3885ed4df72f0780d17f0bb7d Mon Sep 17 00:00:00 2001 From: Jonathan Wyatt Hoech Date: Sun, 29 Jul 2018 22:59:22 -0700 Subject: [PATCH 0115/1825] Writing matrix square root op gradient in batch form. --- tensorflow/python/ops/linalg_grad.py | 99 +++++++++++++++------------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/tensorflow/python/ops/linalg_grad.py b/tensorflow/python/ops/linalg_grad.py index cf03a84ff0..0adfed66aa 100644 --- a/tensorflow/python/ops/linalg_grad.py +++ b/tensorflow/python/ops/linalg_grad.py @@ -54,6 +54,7 @@ def _MatrixDeterminantGrad(op, grad): 0)) return multipliers * a_adj_inv + @ops.RegisterGradient("MatrixSquareRoot") def _MatrixSquareRootGrad(op, grad): """Gradient for MatrixSquareRoot.""" @@ -65,53 +66,57 @@ def _MatrixSquareRootGrad(op, grad): # Solve the resulting Sylvester equation for dR # Used to find Kronecker products within the Sylvester equation - def _KroneckerProduct(mat1, mat2): - """Computes the Kronecker product of two matrices.""" - m1, n1 = mat1.get_shape().as_list() - mat1_rsh = array_ops.reshape(mat1, [m1, 1, n1, 1]) - m2, n2 = mat2.get_shape().as_list() - mat2_rsh = array_ops.reshape(mat2, [1, m2, 1, n2]) - return array_ops.reshape(mat1_rsh * mat2_rsh, [m1 * m2, n1 * n2]) - - sqrt_batch = op.outputs[0] # R - shape_batch = sqrt_batch.get_shape() - order = shape_batch.as_list()[-1] # m - identity = _linalg.eye(order, dtype=sqrt_batch.dtype) # m x m identity matrix - shape_matrix = [order, order] # Tensor may be a batch of matrices of shape m x m - - # Flatten batches containing R and dA - flat_sqrt = array_ops.reshape(sqrt_batch, [-1]) - flat_grad = array_ops.reshape(grad, [-1]) - - # Split flattened batches into m x m matrices - num_elements = flat_sqrt.get_shape().as_list()[-1] - num_splits = int(num_elements / (order * order)) - split_sqrt = array_ops.split(flat_sqrt, num_splits) - split_grad = array_ops.split(flat_grad, num_splits) - - matrix_gradients = [] # Raw gradients of all m x m matrices - for flat_sqrt_matrix, flat_grad_matrix in zip(split_sqrt, split_grad): - sqrt_matrix = array_ops.reshape(flat_sqrt_matrix, shape_matrix) # m x m matrix R - grad_matrix = array_ops.reshape(flat_grad_matrix, shape_matrix) # m x m matrix dA - - # The transpose of R is taken in the k1 term instead of k2 in - # order to prevent redundant transposition of R (i.e. (R')' = R) - sqrt_matrix_transpose = array_ops.matrix_transpose(sqrt_matrix) - k1 = _KroneckerProduct(identity, sqrt_matrix_transpose) - k2 = _KroneckerProduct(sqrt_matrix, identity) - - # Solve for vec(dR) by vectorizing dA - inv_ksum = _linalg.inv(math_ops.add(k1, k2)) - vec_da = array_ops.reshape(array_ops.matrix_transpose(grad_matrix), [-1]) - vec_dsqrt = _linalg.einsum('ij,j->i', inv_ksum, vec_da) # Matrix vector product - - # Solve for dR by inverse vectorizing vec(dR) - dsqrt_transpose = array_ops.reshape(vec_dsqrt, shape_matrix) - dsqrt = array_ops.matrix_transpose(dsqrt_transpose) - matrix_gradients.append(dsqrt) - - # Reshape raw gradients to the original input shape - return array_ops.reshape(array_ops.stack(matrix_gradients), shape_batch) + def _KroneckerProduct(b1, b2): + """Computes the Kronecker product of two batches of square matrices""" + b1_shape = array_ops.shape(b1) + b2_shape = array_ops.shape(b2) + b1_order = b1_shape[-1] + b2_order = b2_shape[-1] + + shape_slice_size = [math_ops.subtract(array_ops.size(b1_shape), 2)] + shape_slice = array_ops.slice(b1_shape, [0], shape_slice_size) # Same for both batches + b1_reshape_shape = array_ops.concat([shape_slice, [b1_order], + [1], [b1_order], [1]], 0) + b2_reshape_shape = array_ops.concat([shape_slice, [1], [b2_order], + [1], [b2_order]], 0) + + b1_reshape = array_ops.reshape(b1, b1_reshape_shape) + b2_reshape = array_ops.reshape(b2, b2_reshape_shape) + + order_prod = b1_order * b2_order + kprod_shape = array_ops.concat([shape_slice, [order_prod], [order_prod]], 0) + return array_ops.reshape(b1_reshape * b2_reshape, kprod_shape) + + sqrtm = op.outputs[0] # R + shape = array_ops.shape(sqrtm) + order = shape[-1] # m + matrix_count = math_ops.reduce_prod(shape[0:-2]) + + # Get batch of m x m identity matrices + eye = linalg_ops.eye(order, dtype=sqrtm.dtype) # m x m identity matrix + eye_flat = array_ops.reshape(eye, [-1]) + eye_tiled = array_ops.tile(eye_flat, [matrix_count]) + eye_batch = array_ops.reshape(eye_tiled, shape) + + # The transpose of R is taken in the k1 term instead of k2 in + # order to prevent redundant transposition of R (i.e. (R')' = R) + sqrtm_transpose = array_ops.matrix_transpose(sqrtm) + k1 = _KroneckerProduct(eye_batch, sqrtm_transpose) + k2 = _KroneckerProduct(sqrtm, eye_batch) + ksum = math_ops.add(k1, k2) + + # Vectorize dA + shape_slice_size = [math_ops.subtract(array_ops.size(shape), 2)] + shape_slice = array_ops.slice(shape, [0], shape_slice_size) + shape_vec_da = array_ops.concat([shape_slice, [order * order], [1]], 0) + vec_da = array_ops.reshape(array_ops.matrix_transpose(grad), shape_vec_da) + + # Solve for vec(dR) + vec_dsqrtm = linalg_ops.matrix_solve(ksum, vec_da) + + # Solve for dR by inverse vectorizing vec(dR) + dsqrtm_transpose = array_ops.reshape(vec_dsqrtm, shape) + return array_ops.matrix_transpose(dsqrtm_transpose) @ops.RegisterGradient("LogMatrixDeterminant") -- GitLab From e0a058a5d229c7bd8de76e476bd124774f49c708 Mon Sep 17 00:00:00 2001 From: Martin Wicke <577277+martinwicke@users.noreply.github.com> Date: Thu, 27 Sep 2018 14:41:41 -0700 Subject: [PATCH 0116/1825] Fix lint --- tensorflow/core/kernels/matrix_square_root_op.cc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tensorflow/core/kernels/matrix_square_root_op.cc b/tensorflow/core/kernels/matrix_square_root_op.cc index a68041a994..359f933fb1 100644 --- a/tensorflow/core/kernels/matrix_square_root_op.cc +++ b/tensorflow/core/kernels/matrix_square_root_op.cc @@ -15,8 +15,6 @@ limitations under the License. // See docs in ../ops/linalg_ops.cc. -#include "third_party/eigen3/Eigen/Core" -#include "third_party/eigen3/unsupported/Eigen/MatrixFunctions" #include "tensorflow/core/framework/kernel_def_builder.h" #include "tensorflow/core/framework/op_kernel.h" #include "tensorflow/core/framework/tensor_shape.h" @@ -25,6 +23,8 @@ limitations under the License. #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/macros.h" #include "tensorflow/core/platform/types.h" +#include "third_party/eigen3/Eigen/Core" +#include "third_party/eigen3/unsupported/Eigen/MatrixFunctions" namespace tensorflow { @@ -33,8 +33,7 @@ class MatrixSquareRootOp : public LinearAlgebraOp { public: INHERIT_LINALG_TYPEDEFS(Scalar); - explicit MatrixSquareRootOp(OpKernelConstruction* context) : Base(context) { - } + explicit MatrixSquareRootOp(OpKernelConstruction* context) : Base(context) {} void ComputeMatrix(OpKernelContext* context, const ConstMatrixMaps& inputs, MatrixMaps* outputs) final { @@ -52,6 +51,8 @@ class MatrixSquareRootOp : public LinearAlgebraOp { REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), float); REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), double); -REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), complex64); -REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), complex128); +REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), + complex64); +REGISTER_LINALG_OP("MatrixSquareRoot", (MatrixSquareRootOp), + complex128); } -- GitLab From e069539898dca8280f909149a5b7796c6e6f70fa Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Mon, 15 Oct 2018 17:47:12 +0100 Subject: [PATCH 0117/1825] Use default device name instead of bind device --- tensorflow/c/c_api.cc | 4 ++-- tensorflow/c/c_api.h | 4 ++-- tensorflow/core/graph/graph_constructor.cc | 8 ++++---- tensorflow/core/graph/graph_constructor.h | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc index 8ba7f26924..f8afe48170 100644 --- a/tensorflow/c/c_api.cc +++ b/tensorflow/c/c_api.cc @@ -1942,9 +1942,9 @@ void TF_ImportGraphDefOptionsSetPrefix(TF_ImportGraphDefOptions* opts, const char* prefix) { opts->opts.prefix = prefix; } -void TF_ImportGraphDefOptionsSetBindDevice(TF_ImportGraphDefOptions* opts, +void TF_ImportGraphDefOptionsSetDefaultDevice(TF_ImportGraphDefOptions* opts, const char* device) { - opts->opts.bind_device = device; + opts->opts.default_device = device; } void TF_ImportGraphDefOptionsSetUniquifyNames(TF_ImportGraphDefOptions* opts, diff --git a/tensorflow/c/c_api.h b/tensorflow/c/c_api.h index cd0a4acf6c..c93a4d226c 100644 --- a/tensorflow/c/c_api.h +++ b/tensorflow/c/c_api.h @@ -900,9 +900,9 @@ TF_CAPI_EXPORT extern void TF_DeleteImportGraphDefOptions( TF_CAPI_EXPORT extern void TF_ImportGraphDefOptionsSetPrefix( TF_ImportGraphDefOptions* opts, const char* prefix); -// Set bind device for the nodes in the `graph_def` that will be imported into `graph`. +// Set default execution device for the nodes in the `graph_def` that will be imported into `graph`. // `device` is copied and has no lifetime requirements. -TF_CAPI_EXPORT extern void TF_ImportGraphDefOptionsSetBindDevice( +TF_CAPI_EXPORT extern void TF_ImportGraphDefOptionsSetDefaultDevice( TF_ImportGraphDefOptions* opts, const char* device); // Set whether to uniquify imported operation names. If true, imported operation diff --git a/tensorflow/core/graph/graph_constructor.cc b/tensorflow/core/graph/graph_constructor.cc index 8a1adb1c92..4e0dcbee4f 100644 --- a/tensorflow/core/graph/graph_constructor.cc +++ b/tensorflow/core/graph/graph_constructor.cc @@ -87,7 +87,7 @@ class GraphConstructor { importing(true), validate_colocation_constraints(in.validate_colocation_constraints), validate_shape(in.validate_shape), - bind_device(in.bind_device) {} + default_device(in.default_device) {} bool allow_internal_ops; bool expect_device_spec; @@ -113,7 +113,7 @@ class GraphConstructor { bool validate_colocation_constraints; bool validate_shape = true; - std::string bind_device; + std::string default_device; }; typedef gtl::ArraySlice NodeDefSlice; @@ -966,8 +966,8 @@ Status GraphConstructor::Convert() { // Note that input_already_exists can grow here AddControlDependencies(&imported_node_def, &input_already_exists); } - if (!opts_.bind_device.empty()) { - imported_node_def.set_device(opts_.bind_device); + if (!opts_.default_device.empty() && imported_node_def.device().empty()) { + imported_node_def.set_device(opts_.default_device); } node_def = &imported_node_def; diff --git a/tensorflow/core/graph/graph_constructor.h b/tensorflow/core/graph/graph_constructor.h index 445be92a0c..94e7eb2ed4 100644 --- a/tensorflow/core/graph/graph_constructor.h +++ b/tensorflow/core/graph/graph_constructor.h @@ -139,8 +139,8 @@ struct ImportGraphDefOptions { // Similar to the producer_op_list argument to import_graph_def in the // python API. - // Try to bind grapth to given device. - std::string bind_device; + // Try to set default execution device for this grapth. + std::string default_device; }; // Optional results that may be returned by ImportGraphDef. -- GitLab From 0e193212fe7bf09f8f19af3ac87ca807bf84a615 Mon Sep 17 00:00:00 2001 From: Deepak B Date: Mon, 15 Oct 2018 10:06:07 -0700 Subject: [PATCH 0118/1825] Delete bug_template.md Removing the bug template. Will add back once Issue Policy is pushed in so that I can add a link to it. --- .github/ISSUE_TEMPLATE/bug_template.md | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_template.md diff --git a/.github/ISSUE_TEMPLATE/bug_template.md b/.github/ISSUE_TEMPLATE/bug_template.md deleted file mode 100644 index c8ab3e2a2c..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_template.md +++ /dev/null @@ -1,24 +0,0 @@ -Please make sure that this is a bug. As per our GitHub Policy [link] we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. - -Please fill the following Bug_Template: -### System information -- Have I written custom code (as opposed to using a stock example script provided in TensorFlow): -- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): -- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: -- TensorFlow installed from (source or binary): -- TensorFlow version (use command below): -- Python version: -- Bazel version (if compiling from source): -- GCC/Compiler version (if compiling from source): -- CUDA/cuDNN version: -- GPU model and memory: -- Docker Image: - -You can use [this script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) to collect some of the information asked above. - -### Describe the current behavior - -### Describe the expected behavior - -### Code to reproduce the issue -Provide a reproducible test case that is the bare minimum necessary to generate the problem. -- GitLab From aa3a7408e6d22cd2cb7c176778474e290bb9ac32 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 15 Oct 2018 10:38:05 -0700 Subject: [PATCH 0119/1825] Remove the temporary logging code and add a flag for Windowns FS --- .../kernels/data/matching_files_dataset_op.cc | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 08953ee390..32aaf6a573 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -120,8 +120,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { Tensor filepath_tensor(ctx->allocator({}), DT_STRING, {}); // Replace the forward slash with the backslash for Windows path - if (dataset()->patterns_[current_pattern_index_ - 1].find('\\') != - std::string::npos) { + if (isWindows_) { std::replace(current_path.first.begin(), current_path.first.end(), '/', '\\'); } @@ -149,6 +148,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // the API expects backslash as an escape character, but no code // appears to rely on this behavior if (current_pattern_.find('\\') != std::string::npos) { + isWindows_ = true; std::replace(current_pattern_.begin(), current_pattern_.end(), '\\', '/'); } @@ -189,6 +189,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { current_pattern_)); TF_RETURN_IF_ERROR( writer->WriteScalar(full_name("hasMatch"), hasMatch_)); + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name("isWindows"), isWindows_)); if (!filepath_queue_.empty()) { TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("queue_size"), @@ -224,6 +226,11 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { reader->ReadScalar(full_name("hasMatch"), &hasMatch)); hasMatch_ = static_cast(hasMatch); + int64 isWindows; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name("isWindows"), &isWindows)); + isWindows_ = static_cast(isWindows); + if (reader->Contains(full_name("queue_size"))) { int64 queue_size; TF_RETURN_IF_ERROR( @@ -269,16 +276,10 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { // If current_path is a directory, search its children. const string& current_dir = current_path.first; std::vector children; - Status s = fs->GetChildren(current_dir, &children); - std::cout << "Children Num: " << children.size() - << "; Status: " << s.ToString() - << "; Current dir: " << current_dir - << "; FileExist status: " - << fs->FileExists(current_dir).ToString() << std::endl; - ret.Update(s); - - // Handle the error cases: 1) continue the search if the status is ok - // or NOT_FOUND; 2) return the non-ok status immediately if it is not + ret.Update(fs->GetChildren(current_dir, &children)); + + // Handle the error cases: 1) continue the search if the status is + // NOT_FOUND; 2) return the non-ok status immediately if it is not // NOT_FOUND. if (ret.code() == error::NOT_FOUND) { continue; @@ -351,6 +352,7 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { size_t current_pattern_index_ GUARDED_BY(mu_) = 0; string current_pattern_ GUARDED_BY(mu_); bool hasMatch_ GUARDED_BY(mu_) = false; + bool isWindows_ GUARDED_BY(mu_) = false; }; const std::vector patterns_; -- GitLab From 7797c8ef36cd8aeb820d9259ea95e12eb80c9497 Mon Sep 17 00:00:00 2001 From: Yifei Feng <1192265+yifeif@users.noreply.github.com> Date: Mon, 15 Oct 2018 11:13:11 -0700 Subject: [PATCH 0120/1825] Remove stale description on old PR process --- CONTRIBUTING.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05e970e8cc..b4d7e58374 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,8 +33,6 @@ just getting started, Github has a [howto](https://help.github.com/articles/usin TensorFlow team members will be assigned to review your pull requests. Once the pull requests are approved and pass continuous integration checks, a TensorFlow team member will apply `ready to pull` label to your change. This means we are working on getting your pull request submitted to our internal repository. After the change has been submitted internally, your pull request will be merged automatically on GitHub. -For some pull requests, we will apply the patch for each pull request to our internal version control system first, and export the change out as a new commit later, at which point the original pull request will be closed. The commits in the pull request will be squashed into a single commit with the pull request creator as the author. These pull requests will be labeled as pending merge internally. - If you want to contribute but you're not sure where to start, take a look at the [issues with the "contributions welcome" label](https://github.com/tensorflow/tensorflow/labels/stat%3Acontributions%20welcome). These are issues that we believe are particularly well suited for outside -- GitLab From f463106698d709a9c2e78f10be5c4999ba1475cb Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Mon, 15 Oct 2018 19:15:05 +0100 Subject: [PATCH 0121/1825] Added go.ImportWithOptions() function and use it to set prefix and default execution device --- tensorflow/go/graph.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/tensorflow/go/graph.go b/tensorflow/go/graph.go index 953ea9ec4e..bbbb87068b 100644 --- a/tensorflow/go/graph.go +++ b/tensorflow/go/graph.go @@ -53,6 +53,17 @@ type Graph struct { c *C.TF_Graph } +// Graph execution options +type GraphImportOptions struct { + // Node prefix + Prefix string + + // Execution device + Device string + + // TODO: extend this structure to support more options from TF_ImportGraphDefOptions +} + // NewGraph returns a new Graph. func NewGraph() *Graph { g := &Graph{C.TF_NewGraph()} @@ -88,22 +99,22 @@ func (g *Graph) WriteTo(w io.Writer) (int64, error) { return int64(n), err } -// Import imports the nodes and edges from a serialized representation of +// ImportWithOptions imports the nodes and edges from a serialized representation of // another Graph into g. // -// Names of imported nodes will be prefixed with prefix. -func (g *Graph) ImportWithDevice(def []byte, prefix string, device string) error { - cprefix := C.CString(prefix) +// Multiple options can be specified for the newly imported nodes. +func (g *Graph) ImportWithOptions(def []byte, options GraphImportOptions) error { + cprefix := C.CString(options.Prefix) defer C.free(unsafe.Pointer(cprefix)) opts := C.TF_NewImportGraphDefOptions() defer C.TF_DeleteImportGraphDefOptions(opts) C.TF_ImportGraphDefOptionsSetPrefix(opts, cprefix) - if len(device) != 0 { - cdev := C.CString(device) + if len(options.Device) != 0 { + cdev := C.CString(options.Device) defer C.free(unsafe.Pointer(cdev)) - C.TF_ImportGraphDefOptionsSetBindDevice(opts, cdev) + C.TF_ImportGraphDefOptionsSetDefaultDevice(opts, cdev) } buf := C.TF_NewBuffer() @@ -129,8 +140,12 @@ func (g *Graph) ImportWithDevice(def []byte, prefix string, device string) error return nil } +// Import imports the nodes and edges from a serialized representation of +// another Graph into g. +// +// Names of imported nodes will be prefixed with prefix. func (g *Graph) Import(def []byte, prefix string) error { - return g.ImportWithDevice(def, prefix, "") + return g.ImportWithOptions(def, GraphImportOptions{Prefix: prefix}) } // Operation returns the Operation named name in the Graph, or nil if no such -- GitLab From 1750163291664f38d6ff15af146d77c5388e6d54 Mon Sep 17 00:00:00 2001 From: Deepak B Date: Mon, 15 Oct 2018 11:58:31 -0700 Subject: [PATCH 0122/1825] Added new issue templates --- .../bug-performance-issue-template.md | 36 +++++++++++++++++++ .../ISSUE_TEMPLATE/bug-performance-issue.md | 34 ++++++++++++++++++ .../build-installation-issue-template.md | 29 +++++++++++++++ .../build-installation-issue.md | 29 +++++++++++++++ .github/ISSUE_TEMPLATE/custom.md | 36 +++++++++++++++++++ .../documentation-issue-template.md | 17 +++++++++ .github/ISSUE_TEMPLATE/documentation-issue.md | 17 +++++++++ .../feature-request-template.md | 22 ++++++++++++ .github/ISSUE_TEMPLATE/feature-request.md | 22 ++++++++++++ .github/ISSUE_TEMPLATE/other-issues.md | 13 +++++++ 10 files changed, 255 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-performance-issue-template.md create mode 100644 .github/ISSUE_TEMPLATE/bug-performance-issue.md create mode 100644 .github/ISSUE_TEMPLATE/build-installation-issue-template.md create mode 100644 .github/ISSUE_TEMPLATE/build-installation-issue.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/documentation-issue-template.md create mode 100644 .github/ISSUE_TEMPLATE/documentation-issue.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request-template.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/other-issues.md diff --git a/.github/ISSUE_TEMPLATE/bug-performance-issue-template.md b/.github/ISSUE_TEMPLATE/bug-performance-issue-template.md new file mode 100644 index 0000000000..890032dba5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-performance-issue-template.md @@ -0,0 +1,36 @@ +--- +name: Bug/Performance Issue Template +about: Use this template for reporting a bug or a performance issue. + +--- + +Please make sure that this is a bug. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. + +Please fill the following Bug_Template: +### System information +- Have I written custom code (as opposed to using a stock example script provided in TensorFlow): +- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): +- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: +- TensorFlow installed from (source or binary): +- TensorFlow version (use command below): +- Python version: +- Bazel version (if compiling from source): +- GCC/Compiler version (if compiling from source): +- CUDA/cuDNN version: +- GPU model and memory: + + +You can collect some of this information using our environment capture [script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) +You can also obtain the TensorFlow version with +python -c "import tensorflow as tf; print(tf.GIT_VERSION, tf.VERSION)" + +### Describe the current behavior + +### Describe the expected behavior + +### Code to reproduce the issue +Provide a reproducible test case that is the bare minimum necessary to generate the problem. + + +### Other info / logs +Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. diff --git a/.github/ISSUE_TEMPLATE/bug-performance-issue.md b/.github/ISSUE_TEMPLATE/bug-performance-issue.md new file mode 100644 index 0000000000..5d6ca6da55 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-performance-issue.md @@ -0,0 +1,34 @@ +--- +name: Bug/Performance Issue +about: Use this template for reporting a bug or a performance issue. + +--- + +Please make sure that this is a bug. As per our [GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:bug_template + +**System information** +- Have I written custom code (as opposed to using a stock example script provided in TensorFlow): +- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): +- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: +- TensorFlow installed from (source or binary): +- TensorFlow version (use command below): +- Python version: +- Bazel version (if compiling from source): +- GCC/Compiler version (if compiling from source): +- CUDA/cuDNN version: +- GPU model and memory: + + +You can collect some of this information using our environment capture [script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) +You can also obtain the TensorFlow version with +python -c "import tensorflow as tf; print(tf.GIT_VERSION, tf.VERSION)" + +**Describe the current behavior** + +**Describe the expected behavior** + +**Code to reproduce the issue** +Provide a reproducible test case that is the bare minimum necessary to generate the problem. + +**Other info / logs** +Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. diff --git a/.github/ISSUE_TEMPLATE/build-installation-issue-template.md b/.github/ISSUE_TEMPLATE/build-installation-issue-template.md new file mode 100644 index 0000000000..61ac00c861 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/build-installation-issue-template.md @@ -0,0 +1,29 @@ +--- +name: Build/Installation Issue Template +about: Use this template for build/installation issues + +--- + +Please make sure that this is a build/installation issue. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:build_template + +**System information** +- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): +- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: +- TensorFlow installed from (source or binary): +- TensorFlow version: +- Python version: +- Installed using virtualenv? pip? conda?: +- Bazel version (if compiling from source): +- GCC/Compiler version (if compiling from source): +- CUDA/cuDNN version: +- GPU model and memory: + + + +**Describe the problem** + +**Provide the exact sequence of commands / steps that you executed before running into the problem** + + +**Any other info / logs** +Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. diff --git a/.github/ISSUE_TEMPLATE/build-installation-issue.md b/.github/ISSUE_TEMPLATE/build-installation-issue.md new file mode 100644 index 0000000000..53e77e32d3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/build-installation-issue.md @@ -0,0 +1,29 @@ +--- +name: Build/Installation Issue +about: Use this template for build/installation issues + +--- + +Please make sure that this is a build/installation issue. As per our [GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:build_template + +**System information** +- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): +- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: +- TensorFlow installed from (source or binary): +- TensorFlow version: +- Python version: +- Installed using virtualenv? pip? conda?: +- Bazel version (if compiling from source): +- GCC/Compiler version (if compiling from source): +- CUDA/cuDNN version: +- GPU model and memory: + + + +**Describe the problem** + +**Provide the exact sequence of commands / steps that you executed before running into the problem** + + +**Any other info / logs** +Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000000..cfbf5d5117 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,36 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. + +--- + +Please make sure that this is a bug. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. + +Please fill the following Bug Template: +### System information +- **Have I written custom code (as opposed to using a stock example script provided in TensorFlow)**: +- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: +- **Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device**: +- **TensorFlow installed from (source or binary)**: +- **TensorFlow version (use command below)**: +- **Python version**: +- **Bazel version (if compiling from source)**: +- **GCC/Compiler version (if compiling from source)**: +- **CUDA/cuDNN version**: +- **GPU model and memory**: + + +You can collect some of this information using our environment capture [script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) +You can also obtain the TensorFlow version with +python -c "import tensorflow as tf; print(tf.GIT_VERSION, tf.VERSION)" + +### Describe the current behavior + +### Describe the expected behavior + +### Code to reproduce the issue +Provide a reproducible test case that is the bare minimum necessary to generate the problem. + + +### Other info / logs +Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. diff --git a/.github/ISSUE_TEMPLATE/documentation-issue-template.md b/.github/ISSUE_TEMPLATE/documentation-issue-template.md new file mode 100644 index 0000000000..7b31d0b3fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation-issue-template.md @@ -0,0 +1,17 @@ +--- +name: Documentation Issue Template +about: Use this template for documentation related issues + +--- + +Please make sure that this is a documentation issue. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:doc_template + + +**System information** +- TensorFlow version: +- Doc Link: + + +**Describe the documentation issue** + +**We welcome contributions by users. Will you be able to update submit a PR to fix the doc Issue?** diff --git a/.github/ISSUE_TEMPLATE/documentation-issue.md b/.github/ISSUE_TEMPLATE/documentation-issue.md new file mode 100644 index 0000000000..8a5fbde645 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation-issue.md @@ -0,0 +1,17 @@ +--- +name: Documentation Issue +about: Use this template for documentation related issues + +--- + +Please make sure that this is a documentation issue. As per our [GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:doc_template + + +**System information** +- TensorFlow version: +- Doc Link: + + +**Describe the documentation issue** + +**We welcome contributions by users. Will you be able to update submit a PR to fix the doc Issue?** diff --git a/.github/ISSUE_TEMPLATE/feature-request-template.md b/.github/ISSUE_TEMPLATE/feature-request-template.md new file mode 100644 index 0000000000..cdcdc3624d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request-template.md @@ -0,0 +1,22 @@ +--- +name: Feature Request Template +about: Use this template for raising a feature request + +--- + +Please make sure that this is a feature request. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:feature_template + + +**System information** +- TensorFlow version (you are using): +- Are you willing to contribute it (Yes/No): + + + +**Describe the feature and the current behavior/state.** + +**Will this change the current api? How?** + +**Who will benefit with this feature?** + +**Any Other info.** diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000000..dbf094daee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,22 @@ +--- +name: Feature Request +about: Use this template for raising a feature request + +--- + +Please make sure that this is a feature request. As per our [GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:feature_template + + +**System information** +- TensorFlow version (you are using): +- Are you willing to contribute it (Yes/No): + + + +**Describe the feature and the current behavior/state.** + +**Will this change the current api? How?** + +**Who will benefit with this feature?** + +**Any Other info.** diff --git a/.github/ISSUE_TEMPLATE/other-issues.md b/.github/ISSUE_TEMPLATE/other-issues.md new file mode 100644 index 0000000000..7cceaf24be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other-issues.md @@ -0,0 +1,13 @@ +--- +name: Other Issues +about: Use this template for any other non-support related issues + +--- + +This template is for miscellaneous issues not covered by the other issue categories. + +For questions on how work with TensorFlow, or support for problems that are not verified bugs in TensorFlow, please go to [StackOverflow](https://stackoverflow.com/questions/tagged/tensorflow). + +If you are reporting a vulnerability, please use the [dedicated reporting process](https://github.com/tensorflow/tensorflow/blob/master/SECURITY.md). + +For high-level discussions about TensorFlow, please post to discuss@tensorflow.org, for questions about the development or internal workings of TensorFlow, or if you would like to know how to contribute to TensorFlow, please post to developers@tensorflow.org. -- GitLab From 3f8506e6057f189d977697f8d0a152d6afbee7c7 Mon Sep 17 00:00:00 2001 From: Deepak B Date: Mon, 15 Oct 2018 11:59:22 -0700 Subject: [PATCH 0123/1825] Delete custom.md --- .github/ISSUE_TEMPLATE/custom.md | 36 -------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/custom.md diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md deleted file mode 100644 index cfbf5d5117..0000000000 --- a/.github/ISSUE_TEMPLATE/custom.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Custom issue template -about: Describe this issue template's purpose here. - ---- - -Please make sure that this is a bug. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. - -Please fill the following Bug Template: -### System information -- **Have I written custom code (as opposed to using a stock example script provided in TensorFlow)**: -- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: -- **Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device**: -- **TensorFlow installed from (source or binary)**: -- **TensorFlow version (use command below)**: -- **Python version**: -- **Bazel version (if compiling from source)**: -- **GCC/Compiler version (if compiling from source)**: -- **CUDA/cuDNN version**: -- **GPU model and memory**: - - -You can collect some of this information using our environment capture [script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) -You can also obtain the TensorFlow version with -python -c "import tensorflow as tf; print(tf.GIT_VERSION, tf.VERSION)" - -### Describe the current behavior - -### Describe the expected behavior - -### Code to reproduce the issue -Provide a reproducible test case that is the bare minimum necessary to generate the problem. - - -### Other info / logs -Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. -- GitLab From 094a06b16300e701188ebe6ba37a4c8d5dfc384b Mon Sep 17 00:00:00 2001 From: Deepak B Date: Mon, 15 Oct 2018 12:00:09 -0700 Subject: [PATCH 0124/1825] Delete bug-performance-issue-template.md --- .../bug-performance-issue-template.md | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-performance-issue-template.md diff --git a/.github/ISSUE_TEMPLATE/bug-performance-issue-template.md b/.github/ISSUE_TEMPLATE/bug-performance-issue-template.md deleted file mode 100644 index 890032dba5..0000000000 --- a/.github/ISSUE_TEMPLATE/bug-performance-issue-template.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Bug/Performance Issue Template -about: Use this template for reporting a bug or a performance issue. - ---- - -Please make sure that this is a bug. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. - -Please fill the following Bug_Template: -### System information -- Have I written custom code (as opposed to using a stock example script provided in TensorFlow): -- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): -- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: -- TensorFlow installed from (source or binary): -- TensorFlow version (use command below): -- Python version: -- Bazel version (if compiling from source): -- GCC/Compiler version (if compiling from source): -- CUDA/cuDNN version: -- GPU model and memory: - - -You can collect some of this information using our environment capture [script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) -You can also obtain the TensorFlow version with -python -c "import tensorflow as tf; print(tf.GIT_VERSION, tf.VERSION)" - -### Describe the current behavior - -### Describe the expected behavior - -### Code to reproduce the issue -Provide a reproducible test case that is the bare minimum necessary to generate the problem. - - -### Other info / logs -Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. -- GitLab From 0e40a0a03c10ef5e6fed9b1bcd2c710932b06c6a Mon Sep 17 00:00:00 2001 From: Deepak B Date: Mon, 15 Oct 2018 12:00:17 -0700 Subject: [PATCH 0125/1825] Delete build-installation-issue-template.md --- .../build-installation-issue-template.md | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/build-installation-issue-template.md diff --git a/.github/ISSUE_TEMPLATE/build-installation-issue-template.md b/.github/ISSUE_TEMPLATE/build-installation-issue-template.md deleted file mode 100644 index 61ac00c861..0000000000 --- a/.github/ISSUE_TEMPLATE/build-installation-issue-template.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Build/Installation Issue Template -about: Use this template for build/installation issues - ---- - -Please make sure that this is a build/installation issue. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:build_template - -**System information** -- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): -- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: -- TensorFlow installed from (source or binary): -- TensorFlow version: -- Python version: -- Installed using virtualenv? pip? conda?: -- Bazel version (if compiling from source): -- GCC/Compiler version (if compiling from source): -- CUDA/cuDNN version: -- GPU model and memory: - - - -**Describe the problem** - -**Provide the exact sequence of commands / steps that you executed before running into the problem** - - -**Any other info / logs** -Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. -- GitLab From 7b7c63e05661b31b7dea1a66b2dbce3fa1262a17 Mon Sep 17 00:00:00 2001 From: Deepak B Date: Mon, 15 Oct 2018 12:00:34 -0700 Subject: [PATCH 0126/1825] Delete documentation-issue-template.md --- .../documentation-issue-template.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/documentation-issue-template.md diff --git a/.github/ISSUE_TEMPLATE/documentation-issue-template.md b/.github/ISSUE_TEMPLATE/documentation-issue-template.md deleted file mode 100644 index 7b31d0b3fe..0000000000 --- a/.github/ISSUE_TEMPLATE/documentation-issue-template.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Documentation Issue Template -about: Use this template for documentation related issues - ---- - -Please make sure that this is a documentation issue. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:doc_template - - -**System information** -- TensorFlow version: -- Doc Link: - - -**Describe the documentation issue** - -**We welcome contributions by users. Will you be able to update submit a PR to fix the doc Issue?** -- GitLab From 05e74beb901a9e4eea16f283bee74ae87f5651be Mon Sep 17 00:00:00 2001 From: Deepak B Date: Mon, 15 Oct 2018 12:00:40 -0700 Subject: [PATCH 0127/1825] Delete feature-request-template.md --- .../feature-request-template.md | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/feature-request-template.md diff --git a/.github/ISSUE_TEMPLATE/feature-request-template.md b/.github/ISSUE_TEMPLATE/feature-request-template.md deleted file mode 100644 index cdcdc3624d..0000000000 --- a/.github/ISSUE_TEMPLATE/feature-request-template.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature Request Template -about: Use this template for raising a feature request - ---- - -Please make sure that this is a feature request. As per our [GitHub Policy](https://github.com/dksb/tensorflow/blob/master/ISSUES.md) we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:feature_template - - -**System information** -- TensorFlow version (you are using): -- Are you willing to contribute it (Yes/No): - - - -**Describe the feature and the current behavior/state.** - -**Will this change the current api? How?** - -**Who will benefit with this feature?** - -**Any Other info.** -- GitLab From cabca3a5e2ba6b54fb9bfab0de66de9e0eec935d Mon Sep 17 00:00:00 2001 From: Deepak B Date: Mon, 15 Oct 2018 12:08:08 -0700 Subject: [PATCH 0128/1825] Update other-issues.md --- .github/ISSUE_TEMPLATE/other-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/other-issues.md b/.github/ISSUE_TEMPLATE/other-issues.md index 7cceaf24be..225962a0f5 100644 --- a/.github/ISSUE_TEMPLATE/other-issues.md +++ b/.github/ISSUE_TEMPLATE/other-issues.md @@ -6,7 +6,7 @@ about: Use this template for any other non-support related issues This template is for miscellaneous issues not covered by the other issue categories. -For questions on how work with TensorFlow, or support for problems that are not verified bugs in TensorFlow, please go to [StackOverflow](https://stackoverflow.com/questions/tagged/tensorflow). +For questions on how to work with TensorFlow, or support for problems that are not verified bugs in TensorFlow, please go to [StackOverflow](https://stackoverflow.com/questions/tagged/tensorflow). If you are reporting a vulnerability, please use the [dedicated reporting process](https://github.com/tensorflow/tensorflow/blob/master/SECURITY.md). -- GitLab From 0d5b9d20cc3e3062aa4d443bc772bb3aed698d15 Mon Sep 17 00:00:00 2001 From: Fei Hu Date: Mon, 15 Oct 2018 14:38:07 -0700 Subject: [PATCH 0129/1825] handle the case that the input patterns contains both windows and other-FS paths --- tensorflow/core/kernels/data/matching_files_dataset_op.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/core/kernels/data/matching_files_dataset_op.cc b/tensorflow/core/kernels/data/matching_files_dataset_op.cc index 32aaf6a573..09517ac264 100644 --- a/tensorflow/core/kernels/data/matching_files_dataset_op.cc +++ b/tensorflow/core/kernels/data/matching_files_dataset_op.cc @@ -151,6 +151,8 @@ class MatchingFilesDatasetOp : public DatasetOpKernel { isWindows_ = true; std::replace(current_pattern_.begin(), current_pattern_.end(), '\\', '/'); + } else { + isWindows_ = false; } StringPiece fixed_prefix = -- GitLab From bf36f984cbc1de40abb2453bceddbf1c37b708e8 Mon Sep 17 00:00:00 2001 From: Brian Nemsick Date: Mon, 15 Oct 2018 15:05:21 -0700 Subject: [PATCH 0130/1825] Add .hdf5 to _is_hdf5_filepath --- tensorflow/python/keras/engine/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index 4d0626cc66..b983055610 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -1674,7 +1674,7 @@ class Network(base_layer.Layer): def _is_hdf5_filepath(filepath): - return filepath.endswith('.h5') or filepath.endswith('.keras') + return filepath.endswith('.h5') or filepath.endswith('.keras') or filepath.endswith('.hdf5') def _make_node_key(layer_name, node_index): -- GitLab From 406e48d628e779581f9a5841ec2c77d5cf339ff7 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Mon, 15 Oct 2018 15:19:05 -0700 Subject: [PATCH 0131/1825] Fix line length lint issue --- tensorflow/python/keras/engine/network.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index b983055610..37cbcd18e6 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -1674,7 +1674,9 @@ class Network(base_layer.Layer): def _is_hdf5_filepath(filepath): - return filepath.endswith('.h5') or filepath.endswith('.keras') or filepath.endswith('.hdf5') + return (filepath.endswith('.h5') + or filepath.endswith('.keras') + or filepath.endswith('.hdf5')) def _make_node_key(layer_name, node_index): -- GitLab From d1679ef92c1a88f95935aeea14b384ad6c7e8084 Mon Sep 17 00:00:00 2001 From: Guangda Lai <31743510+aaroey@users.noreply.github.com> Date: Mon, 15 Oct 2018 15:49:01 -0700 Subject: [PATCH 0132/1825] Fix converter for Const op and add corresponding unit tests. --- tensorflow/contrib/tensorrt/BUILD | 24 + .../contrib/tensorrt/convert/convert_nodes.cc | 921 +++++++----------- .../contrib/tensorrt/convert/convert_nodes.h | 159 +++ .../tensorrt/convert/convert_nodes_test.cc | 646 ++++++++++++ 4 files changed, 1200 insertions(+), 550 deletions(-) create mode 100644 tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD index d756857f18..260294ecd8 100644 --- a/tensorflow/contrib/tensorrt/BUILD +++ b/tensorflow/contrib/tensorrt/BUILD @@ -326,6 +326,30 @@ tf_cuda_cc_test( ]), ) +tf_cuda_cc_test( + name = "convert_nodes_test", + size = "medium", + srcs = ["convert/convert_nodes_test.cc"], + tags = [ + "no_cuda_on_cpu_tap", + "no_windows", + "nomac", + ], + deps = [ + ":trt_logging", + ":trt_conversion", + "//tensorflow/core:core_cpu", + "//tensorflow/core:core_cpu_base", + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + ] + if_tensorrt([ + "@local_config_tensorrt//:nv_infer", + ]), +) + # Library for the segmenting portion of TensorRT operation creation cc_library( name = "segment", diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index b3f5fcd4f9..c5f70fa245 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -17,12 +17,10 @@ limitations under the License. #include #include -#include #include #include #include #include -#include #include #include @@ -87,14 +85,13 @@ using ::tensorflow::str_util::Split; using ::tensorflow::strings::StrAppend; using ::tensorflow::strings::StrCat; -namespace { - inline tensorflow::Status ConvertDType(tensorflow::DataType tf_dtype, nvinfer1::DataType* trt_dtype) { switch (tf_dtype) { case tensorflow::DataType::DT_FLOAT: *trt_dtype = nvinfer1::DataType::kFLOAT; break; + // TODO(aaroey): this should be DT_QINT8 which is not a well supported type. case tensorflow::DataType::DT_INT8: *trt_dtype = nvinfer1::DataType::kINT8; break; @@ -169,13 +166,18 @@ tensorflow::Status ValidateInputProperties(const PartialTensorShape& shape, string DebugString(const nvinfer1::Dims& dims) { string out = StrCat("nvinfer1::Dims(nbDims=", dims.nbDims, ", d="); - for (int i = 0; i < nvinfer1::Dims::MAX_DIMS; ++i) { + for (int i = 0; i < dims.nbDims; ++i) { StrAppend(&out, dims.d[i], ","); } StrAppend(&out, ")"); return out; } +string DebugString(const nvinfer1::ITensor& tensor) { + return StrCat("nvinfer1::ITensor(@", reinterpret_cast(&tensor), + ", shape=", DebugString(tensor.getDimensions()), ")"); +} + // Return whether or not the broadcast is feasible; bool TensorRTGetBroadcastShape(const nvinfer1::Dims& operand_l, const bool operand_l_is_tensor, @@ -268,7 +270,7 @@ inline bool DimsEqual(const nvinfer1::Dims& dim_l, return true; } -inline nvinfer1::Dims GetTensorShape(const tensorflow::Tensor& tensor) { +inline nvinfer1::Dims GetTrtDimsForTensor(const tensorflow::Tensor& tensor) { nvinfer1::Dims dims; dims.nbDims = tensor.dims(); for (int i = 0; i < dims.nbDims; i++) { @@ -277,11 +279,14 @@ inline nvinfer1::Dims GetTensorShape(const tensorflow::Tensor& tensor) { return dims; } -inline int64_t GetShapeSize(const nvinfer1::Dims& shape) { - // Returns total number of elements in shape +// Returns total number of elements in dims. Returning 0 means either some dim +// is 0 or the number of dims is 0. +// Note that for TF scalar constant, we always convert to dims [1]. +int64_t TrtDimsNumElements(const nvinfer1::Dims& dims) { + if (dims.nbDims == 0) return 0; int64_t count = 1; - for (int d = 0; d < shape.nbDims; ++d) { - count *= shape.d[d]; + for (int d = 0; d < dims.nbDims; ++d) { + count *= dims.d[d]; } return count; } @@ -320,133 +325,64 @@ string GetCommonNameScope(const string& op_name_a, const string& op_name_b) { return op_name_a.substr(0, last_scope_separator); } -// Class to convert TF weight to TRT weight. -class TRT_ShapedWeights { - public: - TRT_ShapedWeights(tensorflow::DataType type, const void* values, - nvinfer1::Dims shape) - : shape_(shape), type_(type), values_(values), empty_weight_flag_(false) { - // Note: this->shape.type[] is not used - } - - explicit TRT_ShapedWeights(tensorflow::DataType type) - : shape_(), type_(type), values_(nullptr), empty_weight_flag_(true) {} - - // TODO(aaroey): use rvalue reference. - TRT_ShapedWeights(const TRT_ShapedWeights& rhs) - : shape_(rhs.shape_), - type_(rhs.type_), - values_(rhs.values_), - empty_weight_flag_(rhs.empty_weight_flag_) {} - - // TODO(aaroey): use GetShapeSize() instead. - int64_t count() const { - int64_t c = 1; - for (int i = 0; i < shape_.nbDims; i++) c *= shape_.d[i]; - return c; - } - - nvinfer1::Weights GetWeightsForTRT() const { - nvinfer1::DataType trt_type(nvinfer1::DataType::kFLOAT); - TF_CHECK_OK(ConvertDType(type_, &trt_type)); - if (empty_weight_flag_) return nvinfer1::Weights{trt_type, nullptr, 0}; - - // Note: this->shape.type[] is not used - return nvinfer1::Weights{trt_type, GetValues(), GetShapeSize(shape_)}; - } - - const void* GetValues() const { return values_; } - - // TODO(aaroey): get rid of this method. - void SetValues(const void* values) { values_ = values; } - - size_t size_bytes() const { - int type_size = tensorflow::DataTypeSize(this->type_); - return this->count() * type_size; - } - - // Default converter - operator nvinfer1::Weights() const { return GetWeightsForTRT(); } - - string DebugString() const { - return StrCat( - "TRT_ShapedWeights(shape=", convert::DebugString(shape_), ", type=", - type_, ", values=", reinterpret_cast(values_), - ", empty_weight_flag=", empty_weight_flag_, ")"); - } - - // TODO(aaroey): make these private. - nvinfer1::Dims shape_; - tensorflow::DataType type_; - - private: - // TODO(aaroey): this should not be const as it's always from TRTWeightStore. - const void* values_; - bool empty_weight_flag_; -}; +TRT_ShapedWeights::TRT_ShapedWeights( + tensorflow::DataType type, const void* values, nvinfer1::Dims shape) + : shape_(shape), type_(type), values_(CHECK_NOTNULL(values)) {} -class TRT_TensorOrWeights { - public: - explicit TRT_TensorOrWeights(nvinfer1::ITensor* tensor) - : tensor_(tensor), weights_(DT_FLOAT), variant_(TRT_NODE_TENSOR) {} +TRT_ShapedWeights::TRT_ShapedWeights(tensorflow::DataType type) + : shape_(), type_(type), values_(nullptr) { + shape_.nbDims = 0; +} - explicit TRT_TensorOrWeights(const TRT_ShapedWeights& weights) - : tensor_(nullptr), weights_(weights), variant_(TRT_NODE_WEIGHTS) {} +TRT_ShapedWeights::TRT_ShapedWeights(const TRT_ShapedWeights& rhs) + : shape_(rhs.shape_), type_(rhs.type_), values_(rhs.values_) {} - // TODO(aaroey): use rvalue reference. - TRT_TensorOrWeights(const TRT_TensorOrWeights& rhs) - : tensor_(rhs.tensor_), weights_(rhs.weights_), variant_(rhs.variant_) {} +int64_t TRT_ShapedWeights::count() const { return TrtDimsNumElements(shape_); } - ~TRT_TensorOrWeights() {} +nvinfer1::Weights TRT_ShapedWeights::GetWeightsForTRT() const { + nvinfer1::DataType trt_type(nvinfer1::DataType::kFLOAT); + TF_CHECK_OK(ConvertDType(type_, &trt_type)); + return nvinfer1::Weights{trt_type, values_, values_ == nullptr ? 0 : count()}; +} - bool is_tensor() const { return variant_ == TRT_NODE_TENSOR; } - bool is_weights() const { return variant_ == TRT_NODE_WEIGHTS; } +size_t TRT_ShapedWeights::size_bytes() const { + return this->count() * tensorflow::DataTypeSize(this->type_); +} - nvinfer1::ITensor* tensor() { - CHECK(is_tensor()); - return tensor_; - } +string TRT_ShapedWeights::DebugString() const { + return StrCat( + "TRT_ShapedWeights(shape=", convert::DebugString(shape_), ", type=", + type_, ", values=", reinterpret_cast(values_), ")"); +} - const nvinfer1::ITensor* tensor() const { - CHECK(is_tensor()); - return tensor_; - } +TRT_TensorOrWeights::TRT_TensorOrWeights(nvinfer1::ITensor* tensor) + : tensor_(tensor), weights_(DT_FLOAT), is_tensor_(true) {} - TRT_ShapedWeights& weights() { - CHECK(is_weights()); - return weights_; - } +TRT_TensorOrWeights::TRT_TensorOrWeights(const TRT_ShapedWeights& weights) + : tensor_(nullptr), weights_(weights), is_tensor_(false) {} - const TRT_ShapedWeights& weights() const { - CHECK(is_weights()); - return weights_; - } +TRT_TensorOrWeights::TRT_TensorOrWeights(const TRT_TensorOrWeights& rhs) + : tensor_(rhs.tensor_), weights_(rhs.weights_), is_tensor_(rhs.is_tensor_) {} - nvinfer1::Dims shape() const { - if (is_tensor()) { - return tensor()->getDimensions(); - } else { - return weights().shape_; - } +nvinfer1::Dims TRT_TensorOrWeights::shape() const { + if (is_tensor()) { + return tensor()->getDimensions(); + } else { + return weights().shape_; } +} - string DebugString() const { - string output = "TRT_TensorOrWeights(type="; - if (is_tensor()) { - StrAppend(&output, "tensor @", reinterpret_cast(tensor_), - ", shape=", convert::DebugString(tensor_->getDimensions())); - } else { - StrAppend(&output, "weights=", weights_.DebugString()); - } - StrAppend(&output, ")"); - return output; +string TRT_TensorOrWeights::DebugString() const { + string output = "TRT_TensorOrWeights(type="; + if (is_tensor()) { + StrAppend(&output, "tensor @", reinterpret_cast(tensor_), + ", shape=", convert::DebugString(tensor_->getDimensions())); + } else { + StrAppend(&output, "weights=", weights_.DebugString()); } - - private: - nvinfer1::ITensor* tensor_; - TRT_ShapedWeights weights_; - enum { TRT_NODE_TENSOR, TRT_NODE_WEIGHTS } variant_; -}; + StrAppend(&output, ")"); + return output; +} class TFAttrs { public: @@ -503,12 +439,6 @@ std::vector TFAttrs::get>(const string& key) const { return std::vector(attr.begin(), attr.end()); } -template <> -std::vector TFAttrs::get>(const string& key) const { - auto attr = this->at(key)->list().s(); - return std::vector(attr.begin(), attr.end()); -} - template <> nvinfer1::DataType TFAttrs::get(const string& key) const { nvinfer1::DataType trt_dtype(nvinfer1::DataType::kFLOAT); @@ -640,239 +570,199 @@ void ReorderRSCKToKCRS(const TRT_ShapedWeights& iweights, } } -class Converter; - -using OpConverter = - std::function&, - std::vector*)>; - -class Converter { - public: - explicit Converter(nvinfer1::INetworkDefinition* trt_network, - TRTWeightStore* ws, bool fp16, int max_batch_size) - : trt_network_(trt_network), - weight_store_(ws), - fp16_(fp16), - max_batch_size_(max_batch_size) { - this->register_op_converters(); - } - - TRTWeightStore* weight_store() { return weight_store_; } - - TRT_ShapedWeights get_temp_weights(tensorflow::DataType type, - nvinfer1::Dims shape) { - TRT_ShapedWeights weights(type, nullptr, shape); - // TODO(jie): check weights size_bytes. 0 means type error - weight_store_->store_.push_back(std::vector(weights.size_bytes())); - weights.SetValues(weight_store_->store_.back().data()); - return weights; - } - - // TODO(aaroey): fix all the namings. - bool isFP16() { return fp16_; } - - int GetMaxBatchSize() { return max_batch_size_; } +Converter::Converter(nvinfer1::INetworkDefinition* trt_network, bool fp16, + int max_batch_size) + : trt_network_(trt_network), fp16_(fp16), max_batch_size_(max_batch_size) { + this->RegisterOpConverters(); +} - TRT_ShapedWeights get_temp_weights_like(const TRT_ShapedWeights& weights) { - return this->get_temp_weights(weights.type_, weights.shape_); - } +TRT_ShapedWeights Converter::GetTempWeights(tensorflow::DataType type, + const nvinfer1::Dims& dims) { + const int64_t size_bytes = + TrtDimsNumElements(dims) * tensorflow::DataTypeSize(type); + // TODO(jie): check weights size_bytes. 0 means type error + weight_store_.store_.push_back(std::vector(size_bytes)); + TRT_ShapedWeights weights(type, weight_store_.store_.back().data(), dims); + return weights; +} - tensorflow::Status convert_node(const tensorflow::NodeDef& node_def) { - std::vector inputs; - TF_RETURN_IF_ERROR(this->get_inputs(node_def, &inputs)); - const string& op = node_def.op(); - std::vector outputs; - if (PluginFactoryTensorRT::GetInstance()->IsPlugin(op)) { - TF_RETURN_IF_ERROR(plugin_converter_(*this, node_def, inputs, &outputs)); - } else { - if (!op_registry_.count(op)) { - return tensorflow::errors::Unimplemented( - "No converter registered for op: " + op); +tensorflow::Status Converter::ConvertNode(const tensorflow::NodeDef& node_def) { + std::vector inputs; + TF_RETURN_IF_ERROR(this->GetInputs(node_def, &inputs)); + const string& op = node_def.op(); + std::vector outputs; + if (PluginFactoryTensorRT::GetInstance()->IsPlugin(op)) { + TF_RETURN_IF_ERROR(plugin_converter_(*this, node_def, inputs, &outputs)); + } else { + if (!op_registry_.count(op)) { + return tensorflow::errors::Unimplemented( + "No converter registered for op: " + op); + } + OpConverter op_converter = op_registry_.at(op); + TF_RETURN_IF_ERROR(op_converter(*this, node_def, inputs, &outputs)); + } + for (size_t i = 0; i < outputs.size(); ++i) { + TRT_TensorOrWeights& output = outputs[i]; + string output_name = node_def.name(); + if (i != 0) output_name = StrCat(output_name, ":", i); + // We need to check the name before setting it. For Identity op where the + // output is the input, if its input is one of the engine input, setting + // the name here will overwrite engine input bindings which will cause + // runtime error. + if (output.is_tensor()) { + const char* tensor_name = output.tensor()->getName(); + if (tensor_name == nullptr || std::strlen(tensor_name) == 0) { + output.tensor()->setName(output_name.c_str()); } - OpConverter op_converter = op_registry_.at(op); - TF_RETURN_IF_ERROR(op_converter(*this, node_def, inputs, &outputs)); } - for (size_t i = 0; i < outputs.size(); ++i) { - TRT_TensorOrWeights& output = outputs[i]; - // TODO(jie): tf protobuf seems to be omitting the :0 suffix - string output_name = node_def.name(); - if (i != 0) output_name = StrCat(output_name, ":", i); - // We need to check the name before setting it. For Identity op where the - // output is the input, if its input is one of the engine input, setting - // the name here will overwrite engine input bindings which will cause - // runtime error. - if (output.is_tensor()) { - const char* tensor_name = output.tensor()->getName(); - if (tensor_name == nullptr || std::strlen(tensor_name) == 0) { - output.tensor()->setName(output_name.c_str()); - } - } - VLOG(2) << "Adding out tensor " << output_name << ": " - << output.DebugString(); - if (!trt_tensors_.insert({output_name, output}).second) { - return tensorflow::errors::AlreadyExists( - "Output tensor already exists for op: " + op); - } + VLOG(2) << "Adding out tensor " << output_name << ": " + << output.DebugString(); + if (!trt_tensors_.insert({output_name, output}).second) { + return tensorflow::errors::AlreadyExists( + "Output tensor already exists for op: " + op); } - return tensorflow::Status::OK(); } + return tensorflow::Status::OK(); +} - nvinfer1::INetworkDefinition* network() { return trt_network_; } - - TRT_TensorOrWeights get_tensor(const string& name) { - if (!trt_tensors_.count(name)) { - return TRT_TensorOrWeights(nullptr); - } - return trt_tensors_.at(name); +TRT_TensorOrWeights Converter::GetTensorOrWeights(const string& name) { + if (!trt_tensors_.count(name)) { + return TRT_TensorOrWeights(nullptr); } + return trt_tensors_.at(name); +} - bool insert_input_tensor(const string& name, nvinfer1::ITensor* tensor) { - return trt_tensors_.insert({name, TRT_TensorOrWeights(tensor)}).second; +Status Converter::AddInputTensor( + const string& name, nvinfer1::ITensor* tensor) { + if (!trt_tensors_.insert({name, TRT_TensorOrWeights(tensor)}).second) { + return errors::AlreadyExists("Input tensor already exists for op: ", name); } + return Status::OK(); +} - tensorflow::Status TransposeTensor(nvinfer1::ITensor* input_tensor, - const std::vector& order, - const nvinfer1::ITensor** output_tensor) { - const auto dims = input_tensor->getDimensions(); +Status Converter::TransposeTensor(nvinfer1::ITensor* input_tensor, + const std::vector& order_with_batch_dim, + const nvinfer1::ITensor** output_tensor) { + const auto dims = input_tensor->getDimensions(); - if (order.size() - 1 != size_t(dims.nbDims)) { - return tensorflow::errors::InvalidArgument( + if (order_with_batch_dim.size() - 1 != size_t(dims.nbDims)) { + return tensorflow::errors::InvalidArgument( "Rank of perm for transpose does not match with that of the input."); - } - if (order[0] != 0) { - return tensorflow::errors::Unimplemented( + } + if (order_with_batch_dim[0] != 0) { + return tensorflow::errors::Unimplemented( "Transpose at batch dimension is not supported."); - } - - nvinfer1::IShuffleLayer* layer = this->network()->addShuffle(*input_tensor); - TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Transpose"); + } - nvinfer1::Permutation permutation; - for (int32_t i = 0; i < dims.nbDims; ++i) { - permutation.order[i] = order[i + 1] - 1; - } - layer->setFirstTranspose(permutation); + nvinfer1::IShuffleLayer* layer = this->network()->addShuffle(*input_tensor); + TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Transpose"); - nvinfer1::Dims reshape_dims; - reshape_dims.nbDims = dims.nbDims; - for (int32_t i = 0; i < reshape_dims.nbDims; ++i) { - reshape_dims.d[i] = 0; - reshape_dims.type[i] = dims.type[i]; - } - layer->setReshapeDimensions(reshape_dims); + nvinfer1::Permutation permutation; + for (int32_t i = 0; i < dims.nbDims; ++i) { + permutation.order[i] = order_with_batch_dim[i + 1] - 1; + } + layer->setFirstTranspose(permutation); - *output_tensor = layer->getOutput(0); - return tensorflow::Status::OK(); + nvinfer1::Dims reshape_dims; + reshape_dims.nbDims = dims.nbDims; + for (int32_t i = 0; i < reshape_dims.nbDims; ++i) { + reshape_dims.d[i] = 0; + // TODO(aaroey): why not transposing the types as well? + reshape_dims.type[i] = dims.type[i]; } + layer->setReshapeDimensions(reshape_dims); - // Helper function converts input into tensor with shape specified by dims. - tensorflow::Status PrepareTensorForShape(const TRT_TensorOrWeights& input, - const nvinfer1::Dims& dims, - const nvinfer1::ITensor** tensor) { - // If -1 is not used for one of the dims, we can check if the shapes are - // compatible. - bool can_check_shapes = true; - for (int i = 0; i < dims.nbDims; i++) { - if (dims.d[i] == -1) { - can_check_shapes = false; - break; - } - } - if (can_check_shapes && - GetShapeSize(input.shape()) != GetShapeSize(dims)) { - return tensorflow::errors::InvalidArgument( - "Reshape shapes are not compatible."); + *output_tensor = layer->getOutput(0); + return tensorflow::Status::OK(); +} + +Status Converter::PrepareTensorForShape(const TRT_TensorOrWeights& input, + const nvinfer1::Dims& dims, + const nvinfer1::ITensor** tensor) { + // If -1 is not used for one of the dims, we can check if the shapes are + // compatible. + bool can_check_shapes = true; + for (int i = 0; i < dims.nbDims; i++) { + if (dims.d[i] == -1) { + can_check_shapes = false; + break; } + } + if (can_check_shapes && + TrtDimsNumElements(input.shape()) != TrtDimsNumElements(dims)) { + return tensorflow::errors::InvalidArgument( + "Reshape shapes are not compatible."); + } - if (input.is_tensor()) { - if (DimsEqual(input.shape(), dims)) { - *tensor = input.tensor(); - } else { - nvinfer1::IShuffleLayer* layer = this->network()->addShuffle( - *const_cast(input.tensor())); - TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Reshape"); - layer->setReshapeDimensions(dims); - *tensor = layer->getOutput(0); - } + if (input.is_tensor()) { + if (DimsEqual(input.shape(), dims)) { + *tensor = input.tensor(); } else { -#if NV_TENSORRT_MAJOR > 3 - nvinfer1::IConstantLayer* layer = - this->network()->addConstant(dims, input.weights()); + nvinfer1::IShuffleLayer* layer = this->network()->addShuffle( + *const_cast(input.tensor())); TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Reshape"); + layer->setReshapeDimensions(dims); *tensor = layer->getOutput(0); + } + } else { +#if NV_TENSORRT_MAJOR > 3 + nvinfer1::IConstantLayer* layer = + this->network()->addConstant(dims, input.weights()); + TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Reshape"); + *tensor = layer->getOutput(0); #else - return tensorflow::errors::Unimplemented( + return tensorflow::errors::Unimplemented( "Can't reshape constant. Please upgrade to TRT 4 or above."); #endif - } - return tensorflow::Status::OK(); } + return tensorflow::Status::OK(); +} - private: - std::unordered_map trt_tensors_; - std::unordered_map op_registry_; - OpConverter plugin_converter_; - nvinfer1::INetworkDefinition* trt_network_; - std::list> temp_bufs_; - - // TODO(aaroey): inline the definition of TRTWeightStore here, and add APIs to - // operate the stored weights instead of operating it directly. - TRTWeightStore* weight_store_; - - bool fp16_; - - int max_batch_size_; - - void register_op_converters(); - - tensorflow::Status get_inputs(const tensorflow::NodeDef& node_def, - std::vector* inputs) { - for (auto const& input_name : node_def.input()) { - /************************************************************************* - * TODO(jie): handle case 1) here. - * Normalizes the inputs and extracts associated metadata: - * 1) Inputs can contain a colon followed by a suffix of characters. - * That suffix may be a single number (e.g. inputName:1) or several - * word characters separated from a number by a colon - * (e.g. inputName:foo:1). The - * latter case is used to denote inputs and outputs of functions. - * 2) Control dependency inputs contain caret at the beginning and we - * remove this and annotate the edge as a control dependency. - ************************************************************************/ - // skip control nodes - if (input_name[0] == '^') continue; - string name = input_name; - auto last = name.find_last_of(':'); - // TODO(aaroey): use TensorId - if (last != string::npos && last + 2 == name.size() && - name[last + 1] == '0') { - name.erase(last); - } - - if (trt_tensors_.count(name)) { - TRT_TensorOrWeights& input = trt_tensors_.at(name); - inputs->push_back(input); - VLOG(2) << "Retrieved input " << name << ": " << input.DebugString(); - } else { - // TODO(aaroey): this should not happen, make it a CHECK. - // TODO(aaroey): use StrCat for pattern like this. - string msg("Node "); - StrAppend(&msg, node_def.name(), " should have an input named '", name, - "' but it is not available"); - LOG(ERROR) << msg; - return tensorflow::errors::InvalidArgument(msg); - } +Status Converter::GetInputs(const tensorflow::NodeDef& node_def, + std::vector* inputs) const { + for (auto const& input_name : node_def.input()) { + /************************************************************************* + * TODO(jie): handle case 1) here. + * Normalizes the inputs and extracts associated metadata: + * 1) Inputs can contain a colon followed by a suffix of characters. + * That suffix may be a single number (e.g. inputName:1) or several + * word characters separated from a number by a colon + * (e.g. inputName:foo:1). The + * latter case is used to denote inputs and outputs of functions. + * 2) Control dependency inputs contain caret at the beginning and we + * remove this and annotate the edge as a control dependency. + ************************************************************************/ + // skip control nodes + if (input_name[0] == '^') continue; + string name = input_name; + auto last = name.find_last_of(':'); + // TODO(aaroey): use TensorId + if (last != string::npos && last + 2 == name.size() && + name[last + 1] == '0') { + name.erase(last); + } + + if (trt_tensors_.count(name)) { + TRT_TensorOrWeights input = trt_tensors_.at(name); + inputs->push_back(input); + VLOG(2) << "Retrieved input " << name << ": " << input.DebugString(); + } else { + // TODO(aaroey): this should not happen, make it a CHECK. + // TODO(aaroey): use StrCat for pattern like this. + string msg("Node "); + StrAppend(&msg, node_def.name(), " should have an input named '", name, + "' but it is not available"); + LOG(ERROR) << msg; + return tensorflow::errors::InvalidArgument(msg); } - return tensorflow::Status::OK(); } -}; + return tensorflow::Status::OK(); +} TRT_ShapedWeights ConvertFP32ToFP16(Converter& ctx, const TRT_ShapedWeights& weights_src) { auto dtype_new = tensorflow::DataType::DT_HALF; - TRT_ShapedWeights weights = - ctx.get_temp_weights(dtype_new, weights_src.shape_); + TRT_ShapedWeights weights = ctx.GetTempWeights(dtype_new, weights_src.shape_); const float* src = static_cast(weights_src.GetValues()); Eigen::half* dst = const_cast( static_cast(weights.GetValues())); @@ -1028,77 +918,6 @@ tensorflow::Status UnaryCompute(const TRT_ShapedWeights& iweights, return tensorflow::Status::OK(); } -tensorflow::Status BinaryCompute(const TRT_ShapedWeights& iweights_l, - const TRT_ShapedWeights& iweights_r, - TRT_ShapedWeights* oweights, - LambdaFactory binary_op) { - // Assume iweights_l.type == iweight_r.type - CHECK_EQ(iweights_l.type_, oweights->type_); - CHECK_EQ(iweights_r.type_, oweights->type_); - VLOG(2) << "SANITY CHECK!"; - - switch (iweights_l.type_) { - case tensorflow::DataType::DT_FLOAT: { - auto inp_l = static_cast(iweights_l.GetValues()); - auto inp_r = static_cast(iweights_r.GetValues()); - auto oup = static_cast(const_cast(oweights->GetValues())); - - if (iweights_l.count() != iweights_r.count()) { - // We only supports broadcast of RankZero - if (iweights_l.count() == 1) { - // TODO(aaroey): Remove loggings like this. - VLOG(2) << "I bet it is not working!" << (*inp_l); - std::transform(inp_r, inp_r + iweights_r.count(), oup, - binary_op.broadcast_l(*inp_l)); - } else if (iweights_r.count() == 1) { - VLOG(2) << "I bet it is not working!" << (*inp_r); - std::transform(inp_l, inp_l + iweights_l.count(), oup, - binary_op.broadcast_r(*inp_r)); - } else { - return tensorflow::errors::Unimplemented( - "Binary op with non-rankZero broadcast not supported"); - } - } else { - std::transform(inp_l, inp_l + iweights_l.count(), inp_r, oup, - binary_op.binary()); - } - break; - } - case tensorflow::DataType::DT_HALF: { - auto inp_l = static_cast(iweights_l.GetValues()); - auto inp_r = static_cast(iweights_r.GetValues()); - auto oup = - static_cast(const_cast(oweights->GetValues())); - - if (iweights_l.count() != iweights_r.count()) { - // We only supports broadcast of RankZero - if (iweights_l.count() == 1) { - VLOG(2) << "I bet it is not working!" << (*inp_l); - std::transform(inp_r, inp_r + iweights_r.count(), oup, - binary_op.broadcast_l(*inp_l)); - } else if (iweights_r.count() == 1) { - VLOG(2) << "I bet it is not working!" << (*inp_r); - std::transform(inp_l, inp_l + iweights_l.count(), oup, - binary_op.broadcast_r(*inp_r)); - } else { - return tensorflow::errors::Unimplemented( - "Binary op with non-rankZero broadcast not supported"); - } - } else { - std::transform(inp_l, inp_l + iweights_l.count(), inp_r, oup, - binary_op.binary()); - } - break; - } - default: - return tensorflow::errors::Unimplemented( - "Data type not supported: " + - tensorflow::DataTypeString(iweights_l.type_)); - } - - return tensorflow::Status::OK(); -} - // TODO(jie): broadcast is needed yet not implemented. // Only implemented channel wise for the time being tensorflow::Status BinaryTensorOpWeight( @@ -1207,7 +1026,7 @@ tensorflow::Status BinaryTensorOpWeight( } } - if (ctx.isFP16()) { + if (ctx.IsFP16()) { weights = ConvertFP32ToFP16(ctx, weights); } @@ -1226,7 +1045,7 @@ tensorflow::Status BinaryTensorOpWeight( TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); tensor = layer->getOutput(0); } else { - TRT_ShapedWeights neg_weights = ctx.get_temp_weights_like(weights); + TRT_ShapedWeights neg_weights = ctx.GetTempWeightsLike(weights); LambdaFactory unary_op; unary_op.op = LambdaFactory::OP_CATEGORY::NEG; TF_RETURN_IF_ERROR(UnaryCompute(weights, &neg_weights, unary_op)); @@ -1241,7 +1060,7 @@ tensorflow::Status BinaryTensorOpWeight( TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); tensor = layer->getOutput(0); } else { - TRT_ShapedWeights recip_weights = ctx.get_temp_weights_like(weights); + TRT_ShapedWeights recip_weights = ctx.GetTempWeightsLike(weights); LambdaFactory unary_op; unary_op.op = LambdaFactory::OP_CATEGORY::RECIP; TF_RETURN_IF_ERROR(UnaryCompute(weights, &recip_weights, unary_op)); @@ -1312,11 +1131,11 @@ tensorflow::Status ConvertConv2DHelper( return tensorflow::errors::Internal( "Conv2D expects kernel of dimension 4, at: " + node_def.name()); } - if (ctx.isFP16()) { + if (ctx.IsFP16()) { weights_rsck = ConvertFP32ToFP16(ctx, inputs.at(1).weights()); } - TRT_ShapedWeights weights = ctx.get_temp_weights_like(weights_rsck); + TRT_ShapedWeights weights = ctx.GetTempWeightsLike(weights_rsck); ReorderRSCKToKCRS(weights_rsck, &weights, num_groups); TRT_ShapedWeights biases(weights.type_); const int noutput = weights.shape_.d[0] * num_groups; @@ -1504,7 +1323,7 @@ tensorflow::Status ConvertTranspose( if (inputs.size() != 2 || !inputs.at(0).is_tensor() || !inputs.at(1).is_weights()) { return tensorflow::errors::InvalidArgument( - "Input expects tensor and weights, at", node_def.name()); + "Input expects tensor and weights, at ", node_def.name()); } nvinfer1::ITensor* input_tensor = const_cast( inputs.at(0).tensor()); @@ -1517,11 +1336,6 @@ tensorflow::Status ConvertTranspose( perm[i] = weights_ptr[i]; } - if (perm[0] != 0) { - return tensorflow::errors::Unimplemented( - "Transpose at batch dimension is not supported, at", node_def.name()); - } - const nvinfer1::ITensor* output_tensor = nullptr; TF_RETURN_IF_ERROR(ctx.TransposeTensor(input_tensor, perm, &output_tensor)); outputs->push_back(TRT_TensorOrWeights( @@ -1535,30 +1349,30 @@ tensorflow::Status ConvertReshape( std::vector* outputs) { if (inputs.size() != 2 || !inputs.at(1).is_weights()) { return tensorflow::errors::InvalidArgument( - "Input expects weights for shape, at", node_def.name()); + "Input expects weights for shape, at ", node_def.name()); } TRT_ShapedWeights weights = inputs.at(1).weights(); if (weights.count() == 0) { return tensorflow::errors::Unimplemented( - "Reshape to shape=[] is not supported, at", node_def.name()); + "Reshape to shape=[] is not supported, at ", node_def.name()); } - // Get new_shape + // Get new_dims const int* weights_ptr = static_cast(const_cast( weights.GetValues())); - nvinfer1::Dims new_shape; + nvinfer1::Dims new_dims; // Ignore first (batch) dimension because TRT abstracts batch away - new_shape.nbDims = weights.count() - 1; + new_dims.nbDims = weights.count() - 1; for (int i = 1; i < weights.count(); i++) { - new_shape.d[i-1] = weights_ptr[i]; + new_dims.d[i-1] = weights_ptr[i]; } // Check that batch dimension doesn't change - const nvinfer1::Dims input_shape = inputs.at(0).shape(); + const nvinfer1::Dims input_dims = inputs.at(0).shape(); if (weights_ptr[0] == -1) { - // Product of input shape should equal product of new_shape - if (GetShapeSize(input_shape) != GetShapeSize(new_shape)) { + // Product of input shape should equal product of new_dims + if (TrtDimsNumElements(input_dims) != TrtDimsNumElements(new_dims)) { return tensorflow::errors::Unimplemented( "Reshape on the batch dimension is not supported."); } @@ -1569,7 +1383,7 @@ tensorflow::Status ConvertReshape( const nvinfer1::ITensor* output_tensor = nullptr; TF_RETURN_IF_ERROR( - ctx.PrepareTensorForShape(inputs.at(0), new_shape, &output_tensor)); + ctx.PrepareTensorForShape(inputs.at(0), new_dims, &output_tensor)); outputs->push_back(TRT_TensorOrWeights( const_cast(output_tensor))); return tensorflow::Status::OK(); @@ -1701,7 +1515,7 @@ tensorflow::Status ConvertScale(Converter& ctx, const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); TRT_ShapedWeights weights = inputs.at(1).weights(); - if (ctx.isFP16()) { + if (ctx.IsFP16()) { weights = ConvertFP32ToFP16(ctx, inputs.at(1).weights()); } @@ -1783,140 +1597,153 @@ tensorflow::Status ConvertScale(Converter& ctx, return tensorflow::Status::OK(); } +Status GetTensorDimsWithProtoShape(const Tensor& tensor, + int tensor_proto_array_len, + nvinfer1::Dims* dims) { + if (tensor.dims() > 0) { + *dims = GetTrtDimsForTensor(tensor); + if (TrtDimsNumElements(*dims) != tensor_proto_array_len && + tensor_proto_array_len != 1) { + return errors::InvalidArgument( + "Broadcast on weights only supports kCHANNEL and kUNIFORM"); + } + } else { + dims->nbDims = 1; + // No dimension provided. Flatten it. + dims->d[0] = tensor_proto_array_len; + dims->type[0] = nvinfer1::DimensionType::kSPATIAL; + for (int i = 1; i < nvinfer1::Dims::MAX_DIMS; ++i) { + dims->d[i] = 0; + } + } + return Status::OK(); +} + +template +Status TfTensorToTrtWeights(const DataType dtype, + const Tensor& tensor, + const CType* tensor_proto_array, + int tensor_proto_array_len, Converter* converter, + TRT_ShapedWeights* weights) { + nvinfer1::Dims weight_dims; + TF_RETURN_IF_ERROR(GetTensorDimsWithProtoShape( + tensor, tensor_proto_array_len, &weight_dims)); + const int64_t size_bytes = + tensorflow::DataTypeSize(dtype) * TrtDimsNumElements(weight_dims); + converter->weight_store()->store_.push_back(std::vector(size_bytes)); + void* dst = + static_cast(&(converter->weight_store()->store_.back()[0])); + if (tensor_proto_array_len == 1) { + std::fill_n((CType*)dst, TrtDimsNumElements(weight_dims), + *tensor_proto_array); + } else { + memcpy(dst, tensor_proto_array, size_bytes); + } + *weights = TRT_ShapedWeights(dtype, dst, weight_dims); + return Status::OK(); +} + tensorflow::Status ConvertConst(Converter& ctx, const tensorflow::NodeDef& node_def, const std::vector& inputs, std::vector* outputs) { - const auto& weights_tensor = node_def.attr().at("value").tensor(); + if (!inputs.empty()) { + return errors::InvalidArgument( + "Constant node is expected to have empty input list: ", + node_def.name()); + } - // Get trt type & shape TFAttrs attrs(node_def); - const tensorflow::DataType dtype = attrs.get("dtype"); + const DataType dtype = attrs.get("dtype"); + // We always convert the integer constants to kINT32, since TRT kINT8 is for + // quantized inference. + const DataType converted_dtype = + (dtype == DT_INT16 || dtype == DT_INT8 || dtype == DT_UINT8 ? DT_INT32 + : dtype); + nvinfer1::DataType trt_dtype; + TF_RETURN_IF_ERROR(ConvertDType(converted_dtype, &trt_dtype)); // Create shaped weights as output + const auto& tensor_proto = node_def.attr().at("value").tensor(); tensorflow::Tensor tensor; - if (!tensor.FromProto(weights_tensor)) { + if (!tensor.FromProto(tensor_proto)) { return tensorflow::errors::Internal("Cannot parse weight tensor proto: ", node_def.name()); } - TRT_ShapedWeights weights(dtype); - // TODO(aaroey): we should choose the array using dtype and shape. - if (!weights_tensor.float_val().empty()) { - VLOG(2) << "SCALAR!!!" << node_def.name(); - nvinfer1::Dims scalar_shape; - if (tensor.dims() > 0) { - VLOG(2) << "dimensions: " << tensor.dims(); - VLOG(2) << "size: " << weights_tensor.float_val_size(); - scalar_shape = GetTensorShape(tensor); - VLOG(2) << "details: "; - for (int i = 0; i < scalar_shape.nbDims; i++) - VLOG(2) << scalar_shape.d[i]; - if (GetShapeSize(scalar_shape) != weights_tensor.float_val_size() && - weights_tensor.float_val_size() != 1) { - LOG(ERROR) << "Broadcast on weights only supports kCHANNEL and" - << " kUNIFORM, at: " << node_def.name(); - string err_str("Broadcast method is not supported for '"); - StrAppend(&err_str, node_def.name(), "' of type ", node_def.op()); - return tensorflow::errors::InvalidArgument(err_str); - } - } else { - VLOG(2) << "Dimensions: " << tensor.dims(); - scalar_shape.nbDims = 1; - // no dimension provided. flatten it - scalar_shape.d[0] = weights_tensor.float_val_size(); - scalar_shape.type[0] = nvinfer1::DimensionType::kSPATIAL; - for (int i = 1; i < nvinfer1::Dims::MAX_DIMS; i++) { - scalar_shape.d[i] = 0; + TRT_ShapedWeights weights(converted_dtype); + if (tensor.NumElements() == 0) { + // Do nothing. + } else if (!tensor_proto.float_val().empty()) { + TF_RETURN_IF_ERROR(TfTensorToTrtWeights( + converted_dtype, tensor, tensor_proto.float_val().begin(), + tensor_proto.float_val_size(), &ctx, &weights)); + } else if (!tensor_proto.int_val().empty()) { + TF_RETURN_IF_ERROR(TfTensorToTrtWeights( + converted_dtype, tensor, tensor_proto.int_val().begin(), + tensor_proto.int_val_size(), &ctx, &weights)); + } else if (!tensor_proto.half_val().empty()) { + // TODO(aaroey): implement fp16 conversion. + return errors::Unimplemented("fp16 constant is not supported yet."); + } else if (!tensor_proto.tensor_content().empty()) { + // TODO(aaroey): fp16 will remain in half format and is not converted to + // fp32, but the converter currently uses all float weights as fp32. Fix + // this. + const auto& content = tensor_proto.tensor_content(); + if (content.size() > 0) { + const int dtype_size = tensorflow::DataTypeSize(dtype); + if (content.size() % dtype_size != 0) { + return errors::FailedPrecondition( + "Tensor content size ", content.size(), " is not a multiple of ", + dtype_size); } - } - // TODO(aaroey): use GetShapeSize(). - size_t len_data = tensorflow::DataTypeSize(dtype); - for (int i = 0; i < scalar_shape.nbDims; i++) len_data *= scalar_shape.d[i]; - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - if (weights_tensor.float_val_size() == 1) { - std::fill_n((float*)dst, GetShapeSize(scalar_shape), - *weights_tensor.float_val().begin()); - } else { - // TODO(aaroey): get rid of this copy as RepeatedField is always - // contiguous make a local copy first to flatten doesn't have to be - // contiguous - std::vector tensor_data(weights_tensor.float_val().begin(), - weights_tensor.float_val().end()); - memcpy(dst, tensor_data.data(), len_data); // store into weight store - } - VLOG(2) << "create shape details: "; - for (int i = 0; i < scalar_shape.nbDims; i++) VLOG(2) << scalar_shape.d[i]; - weights = TRT_ShapedWeights(dtype, dst, scalar_shape); - } else if (!weights_tensor.int_val().empty()) { - // TODO(aaroey): this is very similar to the above code for float, merge - // them. - VLOG(2) << "int!!!" << node_def.name(); - nvinfer1::Dims scalar_shape; - if (tensor.dims() > 0) { - VLOG(2) << "dimensions: " << tensor.dims(); - scalar_shape = GetTensorShape(tensor); - if (GetShapeSize(scalar_shape) != weights_tensor.int_val_size() && - weights_tensor.int_val_size() != 1) { - LOG(WARNING) << "Broadcast on weights only supports kCHANNEL and" - << " kUNIFORM, at: " << node_def.name(); - string err_str("Broadcast method is not supported for '"); - StrAppend(&err_str, node_def.name(), "' of type ", node_def.op()); - return tensorflow::errors::InvalidArgument(err_str); + nvinfer1::Dims weights_dim; + TF_RETURN_IF_ERROR(GetTensorDimsWithProtoShape( + tensor, content.size() / dtype_size, &weights_dim)); + const int64_t size_bytes = TrtDimsNumElements(weights_dim) * dtype_size; + if (content.size() != size_bytes) { + return errors::FailedPrecondition( + "Tensor size and TensorProto content size mismatch: ", + size_bytes, " vs ", content.size()); + } else if (tensor.NumElements() != content.size() / dtype_size) { + return errors::FailedPrecondition( + "Tensor elements count and TensorProto content size mismatch: ", + tensor.NumElements(), " vs ", content.size() / dtype_size); } - } else { - VLOG(2) << "dimensions: " << tensor.dims(); - scalar_shape.nbDims = 1; - // no dimension provided. flatten it - scalar_shape.d[0] = weights_tensor.int_val_size(); - scalar_shape.type[0] = nvinfer1::DimensionType::kSPATIAL; - for (int i = 1; i < nvinfer1::Dims::MAX_DIMS; i++) { - scalar_shape.d[i] = 0; - scalar_shape.type[i] = nvinfer1::DimensionType::kSPATIAL; + weights = ctx.GetTempWeights(converted_dtype, weights_dim); + if (dtype_size == tensorflow::DataTypeSize(converted_dtype)) { + port::CopyToArray( + content, + static_cast(const_cast(weights.GetValues()))); + } else { + // Copy out the weights as original data type. + std::vector temp_weights(content.size()); + port::CopyToArray(content, + reinterpret_cast(temp_weights.data())); + int32* dst = + static_cast(const_cast(weights.GetValues())); + // Copy to the weight store as converted data type. + if (dtype == DT_INT16) { + int16* data = reinterpret_cast(temp_weights.data()); + std::copy(data, data + tensor.NumElements(), dst); + } else if (dtype == DT_INT8) { + int8* data = reinterpret_cast(temp_weights.data()); + std::copy(data, data + tensor.NumElements(), dst); + } else if (dtype == DT_UINT8) { + uint8* data = reinterpret_cast(temp_weights.data()); + std::copy(data, data + tensor.NumElements(), dst); + } else { + return errors::FailedPrecondition( + "Unexpected data type: ", DataTypeString(dtype), " at: ", + node_def.name()); + } } } - // we should not have converted - size_t len_data = tensorflow::DataTypeSize(dtype); - for (int i = 0; i < scalar_shape.nbDims; i++) len_data *= scalar_shape.d[i]; - size_t len_tensor = weights_tensor.int_val_size() * sizeof(int32); - len_data = std::max(len_data, len_tensor); - ctx.weight_store()->store_.push_back(std::vector(len_data)); - void* dst = static_cast(&(ctx.weight_store()->store_.back()[0])); - if (weights_tensor.int_val_size() == 1) { - std::fill_n((int*)dst, GetShapeSize(scalar_shape), - *weights_tensor.int_val().begin()); - } else { - // TODO(aaroey): get rid of this copy as RepeatedField is always - // contiguous make a local copy first to flatten doesn't have to be - // contiguous - std::vector tensor_data(weights_tensor.int_val().begin(), - weights_tensor.int_val().end()); - memcpy(dst, tensor_data.data(), len_tensor); // store into weight store - } - weights = TRT_ShapedWeights(dtype, dst, scalar_shape); - } else if (!weights_tensor.tensor_content().empty()) { - // obsolete method. - // After optimization path, we do not see weights in this format. - // TODO(aaroey): why? - // fp16 conversion technically should be needed here. - VLOG(2) << "TENSOR!!!" << node_def.name(); - const auto& content = weights_tensor.tensor_content(); - - weights = ctx.get_temp_weights(dtype, GetTensorShape(tensor)); - if (content.size() > 0) { - const int dtype_size = tensorflow::DataTypeSize(dtype); - CHECK_EQ(0, content.size() % dtype_size) - << "Tensor content size (" << content.size() - << ") is not a multiple of " << dtype_size; - port::CopyToArray( - content, static_cast(const_cast(weights.GetValues()))); - } } else { - return tensorflow::errors::Unimplemented("Not supported constant type, at ", - node_def.name()); + return errors::Unimplemented("Not supported constant type, at ", + node_def.name()); } - // Pass the output + // Pass the output. outputs->push_back(TRT_TensorOrWeights(weights)); return tensorflow::Status::OK(); } @@ -2462,9 +2289,9 @@ tensorflow::Status ConvertFusedBatchNorm( // We could technically have two weights with different shape. // that requires two addScale op, arguably less performant TRT_ShapedWeights combined_scale_weights = - ctx.get_temp_weights_like(*ptr_shape_weights); + ctx.GetTempWeightsLike(*ptr_shape_weights); TRT_ShapedWeights combined_offset_weights = - ctx.get_temp_weights_like(*ptr_shape_weights); + ctx.GetTempWeightsLike(*ptr_shape_weights); const Eigen::half* cast_vals_array[4]; const float* vals_array[4]; @@ -2546,7 +2373,7 @@ tensorflow::Status ConvertMatMulHelper( weights = weights_raw; } else { TRT_ShapedWeights weights_ck = weights_raw; - weights = ctx.get_temp_weights_like(weights_ck); + weights = ctx.GetTempWeightsLike(weights_ck); ReorderCKtoKC(weights_raw, &weights); } TRT_ShapedWeights biases(weights.type_); @@ -2750,7 +2577,7 @@ tensorflow::Status ConvertTopK(Converter& ctx, } #endif -void Converter::register_op_converters() { +void Converter::RegisterOpConverters() { // vgg_16 slim implementation op_registry_["Conv2D"] = ConvertConv2D; op_registry_["DepthwiseConv2dNative"] = ConvertConv2DDepthwise; @@ -2807,8 +2634,6 @@ void Converter::register_op_converters() { plugin_converter_ = ConvertPlugin; } -} // namespace - tensorflow::Status ConvertGraphDefToEngine( const tensorflow::GraphDef& gdef, int precision_mode, int max_batch_size, size_t max_workspace_size_bytes, @@ -2842,11 +2667,10 @@ tensorflow::Status ConvertGraphDefToEngine( return tensorflow::errors::Internal( "Failed to create TensorRT network object"); } - auto ws = std::unique_ptr(new TRTWeightStore()); // Build the network VLOG(1) << "Starting engine conversion "; - Converter converter(trt_network.get(), ws.get(), precision_mode == FP16MODE, + Converter converter(trt_network.get(), precision_mode == FP16MODE, max_batch_size); std::vector> output_tensors; // Graph nodes are already topologically sorted during construction @@ -2891,10 +2715,7 @@ tensorflow::Status ConvertGraphDefToEngine( } VLOG(2) << "Adding engine input tensor " << node_name << " with shape " << DebugString(input_dim); - if (!converter.insert_input_tensor(node_name, input_tensor)) { - return tensorflow::errors::AlreadyExists( - "Output tensor already exists for op: " + node_name); - } + TF_RETURN_IF_ERROR(converter.AddInputTensor(node_name, input_tensor)); } else if (tensorflow::str_util::StartsWith(node_name, kOutputPHName) && (node_def.op() == "Identity")) { int32 slot_number = -1; @@ -2910,11 +2731,11 @@ tensorflow::Status ConvertGraphDefToEngine( } else { VLOG(2) << "Converting node: " << node_def.name() << " , " << node_def.op(); - TF_RETURN_IF_ERROR(converter.convert_node(node_def)); + TF_RETURN_IF_ERROR(converter.ConvertNode(node_def)); } } for (const auto& output : output_tensors) { - auto tensor_or_weights = converter.get_tensor(output.first); + auto tensor_or_weights = converter.GetTensorOrWeights(output.first); if (!tensor_or_weights.is_tensor()) { return tensorflow::errors::InvalidArgument( "Output node '" + output.first + "' is weights not tensor"); diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.h b/tensorflow/contrib/tensorrt/convert/convert_nodes.h index 9274027e63..32f61fdfe5 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.h +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.h @@ -16,6 +16,7 @@ limitations under the License. #ifndef TENSORFLOW_CONTRIB_TENSORRT_CONVERT_CONVERT_NODES_H_ #define TENSORFLOW_CONTRIB_TENSORRT_CONVERT_CONVERT_NODES_H_ +#include #include #include #include @@ -26,6 +27,7 @@ limitations under the License. #include "tensorflow/contrib/tensorrt/log/trt_logger.h" #include "tensorflow/contrib/tensorrt/resources/trt_allocator.h" #include "tensorflow/contrib/tensorrt/resources/trt_int8_calibrator.h" +#include "tensorflow/contrib/tensorrt/resources/trt_resources.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/graph/graph.h" #include "tensorflow/core/grappler/costs/graph_properties.h" @@ -33,6 +35,7 @@ limitations under the License. #if GOOGLE_CUDA #if GOOGLE_TENSORRT +#include "tensorrt/include/NvInfer.h" namespace tensorflow { namespace tensorrt { @@ -170,6 +173,162 @@ class OutputEdgeValidator { bool operator()(const tensorflow::Edge* out_edge) const; }; +//////////////////////////////////////////////////////////////////////////////// +// Classes/functions below are exposed for testing purposes only. +//////////////////////////////////////////////////////////////////////////////// + +string DebugString(const nvinfer1::Dims& dims); +string DebugString(const nvinfer1::ITensor& tensor); +int64_t TrtDimsNumElements(const nvinfer1::Dims& dims); + +// Class to convert TF weight to TRT weight. +class TRT_ShapedWeights { + public: + TRT_ShapedWeights(tensorflow::DataType type, const void* values, + nvinfer1::Dims shape); + + explicit TRT_ShapedWeights(tensorflow::DataType type); + + // TODO(aaroey): use rvalue reference. + TRT_ShapedWeights(const TRT_ShapedWeights& rhs); + + nvinfer1::Weights GetWeightsForTRT() const; + + const void* GetValues() const { return values_; } + + int64_t count() const; + + size_t size_bytes() const; + + // Default converter + operator nvinfer1::Weights() const { return GetWeightsForTRT(); } + + string DebugString() const; + + // TODO(aaroey): make these private. + nvinfer1::Dims shape_; // Note: shape.type[] is not used. + tensorflow::DataType type_; + + private: + // TODO(aaroey): this should not be const as it's always from TRTWeightStore. + const void* values_; + + friend bool operator==(const TRT_ShapedWeights& lhs, + const TRT_ShapedWeights& rhs); +}; + +class TRT_TensorOrWeights { + public: + explicit TRT_TensorOrWeights(nvinfer1::ITensor* tensor); + + explicit TRT_TensorOrWeights(const TRT_ShapedWeights& weights); + + // TODO(aaroey): use rvalue reference. + TRT_TensorOrWeights(const TRT_TensorOrWeights& rhs); + + bool is_tensor() const { return is_tensor_; } + bool is_weights() const { return !is_tensor_; } + + nvinfer1::ITensor* tensor() { + CHECK(is_tensor()); + return tensor_; + } + + const nvinfer1::ITensor* tensor() const { + CHECK(is_tensor()); + return tensor_; + } + + TRT_ShapedWeights& weights() { + CHECK(is_weights()); + return weights_; + } + + const TRT_ShapedWeights& weights() const { + CHECK(is_weights()); + return weights_; + } + + // TODO(aaroey): rename to dims() to be consistent. + nvinfer1::Dims shape() const; + + string DebugString() const; + + private: + nvinfer1::ITensor* tensor_; + TRT_ShapedWeights weights_; + const bool is_tensor_; +}; + +// Class to convert TF nodes to TRT network. +class Converter { + public: + Converter(nvinfer1::INetworkDefinition* trt_network, bool fp16, + int max_batch_size); + + virtual ~Converter() {} + + nvinfer1::INetworkDefinition* network() { return trt_network_; } + + TRTWeightStore* weight_store() { return &weight_store_; } + + bool IsFP16() const { return fp16_; } + + int GetMaxBatchSize() const { return max_batch_size_; } + + TRT_ShapedWeights GetTempWeights(tensorflow::DataType type, + const nvinfer1::Dims& dims); + + TRT_ShapedWeights GetTempWeightsLike(const TRT_ShapedWeights& weights) { + return GetTempWeights(weights.type_, weights.shape_); + } + + Status ConvertNode(const tensorflow::NodeDef& node_def); + + TRT_TensorOrWeights GetTensorOrWeights(const string& name); + + Status AddInputTensor(const string& name, nvinfer1::ITensor* tensor); + + Status TransposeTensor(nvinfer1::ITensor* input_tensor, + const std::vector& order_with_batch_dim, + const nvinfer1::ITensor** output_tensor); + + // Converts input into tensor with shape specified by dims. + Status PrepareTensorForShape(const TRT_TensorOrWeights& input, + const nvinfer1::Dims& dims, + const nvinfer1::ITensor** tensor); + + // Expose for testing purposes. + Status GetInputs(const tensorflow::NodeDef& node_def, + std::vector* inputs) const; + + private: + using OpConverter = std::function&, + std::vector*)>; + + void RegisterOpConverters(); + + std::unordered_map op_registry_; + + std::unordered_map trt_tensors_; + + OpConverter plugin_converter_; + + nvinfer1::INetworkDefinition* trt_network_; + + // TODO(aaroey): inline the definition of TRTWeightStore here, and add APIs to + // operate the stored weights instead of operating it directly. + TRTWeightStore weight_store_; + + bool fp16_; + + int max_batch_size_; + + friend class ConverterForTest; +}; + } // namespace convert } // namespace tensorrt } // namespace tensorflow diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc new file mode 100644 index 0000000000..5c9ddaec49 --- /dev/null +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc @@ -0,0 +1,646 @@ +/* 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/convert/convert_nodes.h" + +#include +#include +#include + +#include +#include +#include "tensorflow/contrib/tensorrt/log/trt_logger.h" +#include "tensorflow/contrib/tensorrt/plugin/trt_plugin_factory.h" +#include "tensorflow/core/framework/tensor.h" +#include "tensorflow/core/framework/tensor.pb.h" +#include "tensorflow/core/framework/tensor_shape.h" +#include "tensorflow/core/framework/tensor_testutil.h" +#include "tensorflow/core/lib/core/error_codes.pb.h" +#include "tensorflow/core/lib/core/status.h" +#include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/platform/test.h" + +#if GOOGLE_CUDA +#if GOOGLE_TENSORRT +#include "cuda/include/cuda.h" +#include "cuda/include/cuda_runtime_api.h" +#include "tensorrt/include/NvInfer.h" + +namespace tensorflow { +namespace tensorrt { +namespace convert { + +using ::testing::ElementsAre; + +void ExpectStatus(Status status, error::Code code, const char* substr) { + EXPECT_EQ(code, status.code()) << status; + EXPECT_THAT(status.error_message(), ::testing::HasSubstr(substr)) << status; +} + +nvinfer1::Dims GetTestDims(const std::vector& d) { + nvinfer1::Dims dims; + dims.nbDims = d.size(); + for (int i = 0; i < d.size(); ++i) { + dims.d[i] = d[i]; + } + return dims; +} + +// Fake ITensor implementation for testing purposes. +class FakeITensor : public nvinfer1::ITensor { + public: + FakeITensor() {} + + FakeITensor(const nvinfer1::Dims& dims, const string& name = "") + : name_(name), dims_(dims) {} + + FakeITensor(const string& name, const std::vector& dims) + : name_(name), dims_(GetTestDims(dims)) {} + + void SetDims(const std::vector& dims) { + setDimensions(GetTestDims(dims)); + } + + void setName(const char* name) override { name_ = name; } + + const char* getName() const override { return name_.c_str(); } + + void setDimensions(nvinfer1::Dims dimensions) override { dims_ = dimensions; } + + nvinfer1::Dims getDimensions() const override { return dims_; } + + void setType(nvinfer1::DataType type) override { type_ = type; } + + nvinfer1::DataType getType() const override { return type_; } + + bool isNetworkInput() const override { return false; } + + bool isNetworkOutput() const override { return false; } + + void setBroadcastAcrossBatch(bool broadcastAcrossBatch) override {} + + bool getBroadcastAcrossBatch() const override { return false; } + + nvinfer1::TensorLocation getLocation() const override { return location_; } + + void setLocation(nvinfer1::TensorLocation location) override { + location_ = location; + } + + private: + string name_; + nvinfer1::Dims dims_; + nvinfer1::DataType type_; + nvinfer1::TensorLocation location_; +}; + +bool Equals(const nvinfer1::Dims& lhs, const nvinfer1::Dims& rhs) { + if (lhs.nbDims != rhs.nbDims) return false; + for (int i = 0; i < lhs.nbDims; ++i) { + if (lhs.d[i] != rhs.d[i]) return false; + // We don't check the types in the tests. + } + return true; +} + +bool operator==(const TRT_ShapedWeights& lhs, const TRT_ShapedWeights& rhs) { + return Equals(lhs.shape_, rhs.shape_) && lhs.type_ == rhs.type_ && + lhs.values_ == rhs.values_; +} + +TEST(TRT_ShapedWeights_Test, Basic) { + { + float raw_weights[10]; + TRT_ShapedWeights weights(DT_FLOAT, raw_weights, GetTestDims({2, 5})); + + nvinfer1::Weights trt_weights = weights.GetWeightsForTRT(); + EXPECT_EQ(nvinfer1::DataType::kFLOAT, trt_weights.type); + EXPECT_EQ(static_cast(raw_weights), trt_weights.values); + EXPECT_EQ(10, trt_weights.count); + + EXPECT_EQ(static_cast(raw_weights), weights.GetValues()); + EXPECT_EQ(10, weights.count()); + EXPECT_EQ(40, weights.size_bytes()); + } + { + int32 raw_weights = 0; + TRT_ShapedWeights weights(DT_INT32, &raw_weights, GetTestDims({1, 1, 1})); + + nvinfer1::Weights trt_weights = weights.GetWeightsForTRT(); + EXPECT_EQ(nvinfer1::DataType::kINT32, trt_weights.type); + EXPECT_EQ(static_cast(&raw_weights), trt_weights.values); + EXPECT_EQ(1, trt_weights.count); + + EXPECT_EQ(static_cast(&raw_weights), weights.GetValues()); + EXPECT_EQ(1, weights.count()); + EXPECT_EQ(4, weights.size_bytes()); + } + { + TRT_ShapedWeights weights(DT_FLOAT); + + nvinfer1::Weights trt_weights = weights.GetWeightsForTRT(); + EXPECT_EQ(nvinfer1::DataType::kFLOAT, trt_weights.type); + EXPECT_EQ(nullptr, trt_weights.values); + EXPECT_EQ(0, trt_weights.count); + + EXPECT_EQ(nullptr, weights.GetValues()); + EXPECT_EQ(0, weights.count()); + EXPECT_EQ(0, weights.size_bytes()); + } +} + +TEST(TRT_TensorOrWeights_Test, Basic) { + { + nvinfer1::Dims dims; + dims.nbDims = 1; + dims.d[0] = 1; + FakeITensor itensor(dims); + + TRT_TensorOrWeights tw(&itensor); + EXPECT_EQ(true, tw.is_tensor()); + EXPECT_EQ(false, tw.is_weights()); + EXPECT_EQ(&itensor, tw.tensor()); + EXPECT_TRUE(Equals(dims, tw.shape())) + << "- expected: " << DebugString(dims) + << "\n vs\n- actual: " << DebugString(tw.shape()); + } + { + TRT_ShapedWeights weights(DT_FLOAT); + TRT_TensorOrWeights tw(weights); + EXPECT_EQ(false, tw.is_tensor()); + EXPECT_EQ(true, tw.is_weights()); + EXPECT_EQ(weights, tw.weights()); + + nvinfer1::Dims dims; + dims.nbDims = 0; + EXPECT_TRUE(Equals(dims, tw.shape())) + << "- expected: " << DebugString(dims) + << "\n vs\n- actual: " << DebugString(tw.shape()); + } +} + +class ConverterForTest : public Converter { + public: + ConverterForTest() + : Converter(nullptr, /*fp16=*/false, /*max_batch_size=*/1) { + QCHECK_EQ(0, cudaStreamCreate(&stream_)); + Reset(); + } + + ~ConverterForTest() override { QCHECK_EQ(0, cudaStreamDestroy(stream_)); } + + // Helper methods for testing purposes. + + void AddOpConverter(const string& op_name, OpConverter op_converter) { + op_registry_[op_name] = op_converter; + } + + void AddTensorOrWeights(const string& name, TRT_TensorOrWeights tw) { + ASSERT_TRUE(trt_tensors_.insert({name, tw}).second); + } + + void Reset() { + // Clear the tensor map. + trt_tensors_.clear(); + // Reset the INetworkDefinition. + engine_.reset(nullptr); + network_.reset(nullptr); + builder_.reset(nullptr); + builder_.reset(nvinfer1::createInferBuilder(logger_)); + network_.reset(builder_->createNetwork()); + trt_network_ = network_.get(); + } + + void BuildAndRun(const char* input_name, const std::vector& input_data, + const char* output_name, std::vector* output_data) { + // Mark the output tensor as TRT engine output. + TRT_TensorOrWeights tensor = GetTensorOrWeights(output_name); + tensor.tensor()->setName(output_name); + network()->markOutput(*tensor.tensor()); + + // Build the TRT engine. + QCHECK_EQ(nullptr, engine_.get()); + engine_.reset(builder_->buildCudaEngine(*network())); + CHECK_NOTNULL(engine_.get()); + + // Execute the TRT engine. + const int input_size = input_data.size() * sizeof(float); + const int output_size = output_data->size() * sizeof(float); + const int input_index = engine_->getBindingIndex(input_name); + const int output_index = engine_->getBindingIndex(output_name); + + ASSERT_EQ(engine_->getNbBindings(), 2); + void* buffers[2]; + ASSERT_EQ(0, cudaMalloc(&buffers[input_index], input_size)); + ASSERT_EQ(0, cudaMalloc(&buffers[output_index], output_size)); + ASSERT_EQ(0, cudaMemcpyAsync(buffers[input_index], input_data.data(), + input_size, cudaMemcpyHostToDevice, stream_)); + TrtUniquePtrType execution_context( + engine_->createExecutionContext()); + execution_context->enqueue(1, buffers, stream_, nullptr); + ASSERT_EQ(0, cudaMemcpyAsync(output_data->data(), buffers[output_index], + output_size, cudaMemcpyDeviceToHost, stream_)); + cudaStreamSynchronize(stream_); + ASSERT_EQ(0, cudaFree(buffers[input_index])); + ASSERT_EQ(0, cudaFree(buffers[output_index])); + } + + private: + Logger logger_; + TrtUniquePtrType builder_; + TrtUniquePtrType network_; + TrtUniquePtrType engine_; + cudaStream_t stream_; +}; + +class ConverterTest : public ::testing::Test { + protected: + nvinfer1::ITensor* AddTestTensor(const char* name, + const std::vector& dims) { + nvinfer1::ITensor* tensor = converter_.network()->addInput( + name, nvinfer1::DataType::kFLOAT, GetTestDims(dims)); + converter_.AddTensorOrWeights(name, TRT_TensorOrWeights{tensor}); + return tensor; + } + + template + TRT_ShapedWeights AddTestWeights(const char* name, const DataType dtype, + const std::vector& dims, + const std::vector& values) { + const nvinfer1::Dims trt_dims = GetTestDims(dims); + const int64_t num_elements = TrtDimsNumElements(trt_dims); + QCHECK_EQ(num_elements, values.size()) + << num_elements << " vs " << values.size(); + TRT_ShapedWeights weights(dtype); + if (num_elements) { + const int64_t size_bytes = DataTypeSize(dtype) * num_elements; + QCHECK_EQ(size_bytes, sizeof(CType) * values.size()) + << size_bytes << " vs " << sizeof(CType) * values.size(); + converter_.weight_store()->store_.push_back( + std::vector(size_bytes)); + void* dst = + static_cast(converter_.weight_store()->store_.back().data()); + memcpy(dst, values.data(), size_bytes); + weights = TRT_ShapedWeights(dtype, dst, trt_dims); + } + converter_.AddTensorOrWeights(name, TRT_TensorOrWeights{weights}); + return weights; + } + + NodeDef MakeNodeDef(const string& name, const string& op, + const std::vector& inputs) { + NodeDef node_def; + node_def.set_name(name); + node_def.set_op(op); + for (const string& input : inputs) { + node_def.add_input(input); + } + return node_def; + } + + ConverterForTest converter_; +}; + +TEST_F(ConverterTest, GetTempWeights) { + TRT_ShapedWeights weights = + converter_.GetTempWeights(DT_FLOAT, GetTestDims({2, 3})); + + nvinfer1::Weights trt_weights = weights.GetWeightsForTRT(); + EXPECT_EQ(nvinfer1::DataType::kFLOAT, trt_weights.type); + EXPECT_NE(nullptr, trt_weights.values); + EXPECT_EQ(6, trt_weights.count); + + EXPECT_NE(nullptr, weights.GetValues()); + EXPECT_EQ(6, weights.count()); + EXPECT_EQ(24, weights.size_bytes()); + + // TODO(aaroey): test the case where shape element count is 0. +} + +TEST_F(ConverterTest, GetInputs) { + NodeDef node_def; + node_def.add_input("^control_input"); + node_def.add_input("input"); + node_def.add_input("input:0"); + node_def.add_input("input:1"); + node_def.add_input("weird_input:2:3:4:0"); + + FakeITensor input, input_1, input_2; + TF_EXPECT_OK(converter_.AddInputTensor("input", &input)); + TF_EXPECT_OK(converter_.AddInputTensor("input:1", &input_1)); + TF_EXPECT_OK(converter_.AddInputTensor("weird_input:2:3:4", &input_2)); + + std::vector inputs; + TF_EXPECT_OK(converter_.GetInputs(node_def, &inputs)); + EXPECT_EQ(4, inputs.size()); + EXPECT_EQ(&input, inputs[0].tensor()); + EXPECT_EQ(&input, inputs[1].tensor()); + EXPECT_EQ(&input_1, inputs[2].tensor()); + EXPECT_EQ(&input_2, inputs[3].tensor()); +} + +TEST_F(ConverterTest, ConvertNode) { + FakeITensor output_tensors[2]; + auto op_converter = [&output_tensors]( + Converter& ctx, const NodeDef& node_def, + const std::vector& inputs, + std::vector* outputs) -> Status { + nvinfer1::Dims dims = inputs[0].tensor()->getDimensions(); + for (int i = 0; i < 2; ++i) { + dims.d[0] += 1; + output_tensors[i].setDimensions(dims); + outputs->push_back(TRT_TensorOrWeights(&output_tensors[i])); + } + return Status::OK(); + }; + converter_.AddOpConverter("MyOp", op_converter); + + FakeITensor input_tensor("my_input", {12345}); + TF_EXPECT_OK(converter_.AddInputTensor("my_input", &input_tensor)); + + NodeDef node_def = MakeNodeDef("my_op", "MyOp", {"my_input"}); + TF_EXPECT_OK(converter_.ConvertNode(node_def)); + + TRT_TensorOrWeights actual_output_1 = converter_.GetTensorOrWeights("my_op"); + EXPECT_EQ(&output_tensors[0], actual_output_1.tensor()); + EXPECT_EQ(12346, actual_output_1.tensor()->getDimensions().d[0]); + + TRT_TensorOrWeights actual_output_2 = + converter_.GetTensorOrWeights("my_op:1"); + EXPECT_EQ(&output_tensors[1], actual_output_2.tensor()); + EXPECT_EQ(12347, actual_output_2.tensor()->getDimensions().d[0]); +} + +TEST_F(ConverterTest, TransposeTensor) { + nvinfer1::ITensor* input_tensor = AddTestTensor("", {2, 3, 5}); + const nvinfer1::ITensor* output_tensor = nullptr; + + // Rank doesn't match. + ExpectStatus( + converter_.TransposeTensor(input_tensor, {0, 1}, &output_tensor), + error::INVALID_ARGUMENT, + "Rank of perm for transpose does not match with that of the input"); + + // Transpose at batch dimension. + ExpectStatus( + converter_.TransposeTensor(input_tensor, {1, 0, 2, 3}, &output_tensor), + error::UNIMPLEMENTED, "Transpose at batch dimension is not supported."); + + // OK. + TF_EXPECT_OK( + converter_.TransposeTensor(input_tensor, {0, 3, 1, 2}, &output_tensor)); + EXPECT_TRUE(Equals(GetTestDims({5, 2, 3}), output_tensor->getDimensions())) + << DebugString(*output_tensor); +} + +TEST_F(ConverterTest, PrepareTensorForShape_Tensor) { + nvinfer1::ITensor* input_tensor = AddTestTensor("", {2, 3, 5}); + TRT_TensorOrWeights tw(input_tensor); + const nvinfer1::ITensor* output_tensor = nullptr; + + // Shape size doesn't match. + ExpectStatus(converter_.PrepareTensorForShape(tw, GetTestDims({2, 3, 6}), + &output_tensor), + error::INVALID_ARGUMENT, "Reshape shapes are not compatible."); + + // TODO(aaroey): we should check the case where uninferred dimensions are not + // an exact divisor of input dim ensions, e.g. for dims {-1, 7}. + + // Infer shape, ok. + TF_EXPECT_OK(converter_.PrepareTensorForShape(tw, GetTestDims({-1, 2}), + &output_tensor)); + EXPECT_TRUE(Equals(GetTestDims({15, 2}), output_tensor->getDimensions())) + << DebugString(*output_tensor); + + // Regular shape. + TF_EXPECT_OK(converter_.PrepareTensorForShape(tw, GetTestDims({10, 3}), + &output_tensor)); + EXPECT_TRUE(Equals(GetTestDims({10, 3}), output_tensor->getDimensions())) + << DebugString(*output_tensor); +} + +#if NV_TENSORRT_MAJOR > 3 +TEST_F(ConverterTest, PrepareTensorForShape_Weights) { + TRT_ShapedWeights weights = + converter_.GetTempWeights(DT_FLOAT, GetTestDims({2, 3, 5})); + TRT_TensorOrWeights tw(weights); + const nvinfer1::ITensor* output_tensor = nullptr; + TF_EXPECT_OK(converter_.PrepareTensorForShape(tw, GetTestDims({10, 3}), + &output_tensor)); + EXPECT_TRUE(Equals(GetTestDims({10, 3}), output_tensor->getDimensions())) + << DebugString(*output_tensor); +} +#endif + +template +void TestConvertConst(ConverterForTest* converter) { + NodeDef node_def; + node_def.set_name("my_const"); + node_def.set_op("Const"); + + auto reset_and_test = [&node_def, converter]( + const Tensor& tensor, const bool as_tensor_content, + const std::vector& expected_dims, + const std::vector& expected_value) { + converter->Reset(); + + auto& attr = *node_def.mutable_attr(); + if (as_tensor_content) { + tensor.AsProtoTensorContent(attr["value"].mutable_tensor()); + } else { + tensor.AsProtoField(attr["value"].mutable_tensor()); + } + TF_EXPECT_OK(converter->ConvertNode(node_def)); + TRT_TensorOrWeights output = converter->GetTensorOrWeights("my_const"); + EXPECT_TRUE(Equals(GetTestDims(expected_dims), output.weights().shape_)) + << output.DebugString(); + ASSERT_EQ(expected_value.size(), output.weights().count()) + << output.DebugString(); + const OutputCType* actual_values = + static_cast(output.weights().GetValues()); + for (int i = 0; i < expected_value.size(); ++i) { + EXPECT_EQ(expected_value[i], actual_values[i]); + } + }; + + auto& attr = *node_def.mutable_attr(); + attr["dtype"].set_type(dtype); + { + // By default empty tensor will pick DT_FLOAT as data type and we fix it + // here. + attr["value"].mutable_tensor()->set_dtype(dtype); + Tensor t; // Empty tensor. + reset_and_test(t, false, {}, {}); + } + { + Tensor t = ::tensorflow::test::AsScalar(12); + reset_and_test(t, false, {1}, {12}); + reset_and_test(t, true, {1}, {12}); + } + { + Tensor t = ::tensorflow::test::AsTensor({1, 2}); + reset_and_test(t, false, {2}, {1, 2}); + reset_and_test(t, true, {2}, {1, 2}); + } + { + Tensor t = ::tensorflow::test::AsTensor({1, 2, 3, 4, 5, 6}, + TensorShape({2, 3})); + reset_and_test(t, false, {2, 3}, {1, 2, 3, 4, 5, 6}); + reset_and_test(t, true, {2, 3}, {1, 2, 3, 4, 5, 6}); + } +} + +TEST_F(ConverterTest, ConvertConst) { + { + converter_.Reset(); + NodeDef node_def = MakeNodeDef("my_const", "Const", {"input"}); + AddTestTensor("input", {1}); + ExpectStatus( + converter_.ConvertNode(node_def), error::INVALID_ARGUMENT, + "Constant node is expected to have empty input list: my_const"); + } + { + converter_.Reset(); + NodeDef node_def = MakeNodeDef("my_const", "Const", {}); + (*node_def.mutable_attr())["dtype"].set_type(DT_DOUBLE); + ExpectStatus(converter_.ConvertNode(node_def), error::INVALID_ARGUMENT, + "Unsupported data type"); + } + + TestConvertConst(&converter_); + TestConvertConst(&converter_); +#if NV_TENSORRT_MAJOR > 3 + TestConvertConst(&converter_); +#endif +} + +TEST_F(ConverterTest, ConvertTranspose) { + { + // Input list is empty, should fail. + NodeDef node_def = MakeNodeDef("my_transpose", "Transpose", {}); + ExpectStatus(converter_.ConvertNode(node_def), error::INVALID_ARGUMENT, + "Input expects tensor and weights, at my_transpose"); + } + NodeDef node_def = + MakeNodeDef("my_transpose", "Transpose", {"input", "weights"}); + { + // Permutation is a tensor, should fail. + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestTensor("weights", {3}); + ExpectStatus(converter_.ConvertNode(node_def), error::INVALID_ARGUMENT, + "Input expects tensor and weights, at my_transpose"); + } + { + // Transpose at batch dimension, should fail. + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", DT_INT32, {4}, {1, 0, 2, 3}); + ExpectStatus(converter_.ConvertNode(node_def), error::UNIMPLEMENTED, + "Transpose at batch dimension is not supported"); + } + { + // Permutation rank doesn't match, should fail. + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", DT_INT32, {3}, {0, 1, 2}); + ExpectStatus( + converter_.ConvertNode(node_def), error::INVALID_ARGUMENT, + "Rank of perm for transpose does not match with that of the input."); + } + { + // Ok. + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", DT_INT32, {4}, {0, 3, 1, 2}); + TF_EXPECT_OK(converter_.ConvertNode(node_def)); + TRT_TensorOrWeights output = converter_.GetTensorOrWeights("my_transpose"); + EXPECT_TRUE(output.is_tensor()); + EXPECT_TRUE( + Equals(GetTestDims({3, 1, 2}), output.tensor()->getDimensions())) + << output.DebugString(); + + std::vector output_data(6); + converter_.BuildAndRun("input", {1, 2, 3, 4, 5, 6}, "my_transpose", + &output_data); + EXPECT_THAT(output_data, ElementsAre(1, 4, 2, 5, 3, 6)); + } +} + +TEST_F(ConverterTest, ConvertReshape) { + { + // Input list is empty, should fail. + NodeDef node_def = MakeNodeDef("my_reshape", "Reshape", {}); + ExpectStatus(converter_.ConvertNode(node_def), error::INVALID_ARGUMENT, + "Input expects weights for shape, at my_reshape"); + } + NodeDef node_def = MakeNodeDef("my_reshape", "Reshape", {"input", "weights"}); + { + // Shape is a tensor, should fail. + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestTensor("weights", {3}); + ExpectStatus(converter_.ConvertNode(node_def), error::INVALID_ARGUMENT, + "Input expects weights for shape, at my_reshape"); + } + { + // Reshape to scalar, should fail. + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", DT_INT32, {}, {}); + ExpectStatus(converter_.ConvertNode(node_def), error::UNIMPLEMENTED, + "Reshape to shape=[] is not supported, at my_reshape"); + } + { + // Reshape at batch dimension, should fail. + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", DT_INT32, {4}, {-1, 1, 1, 2}); + ExpectStatus(converter_.ConvertNode(node_def), error::UNIMPLEMENTED, + "Reshape on the batch dimension is not supported"); + } + { + // Reshape at batch dimension, should fail. + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", DT_INT32, {4}, {3, 1, 1, 2}); + ExpectStatus(converter_.ConvertNode(node_def), error::UNIMPLEMENTED, + "Reshape on the batch dimension is not supported"); + } + // Reshape on non batch dimensions, ok. + for (int batch_dim : {-1, 1}) { + converter_.Reset(); + AddTestTensor("input", {1, 2, 3}); + AddTestWeights("weights", DT_INT32, {4}, {batch_dim, 1, 3, 2}); + TF_EXPECT_OK(converter_.ConvertNode(node_def)); + TRT_TensorOrWeights output = converter_.GetTensorOrWeights("my_reshape"); + EXPECT_TRUE(output.is_tensor()); + EXPECT_TRUE( + Equals(GetTestDims({1, 3, 2}), output.tensor()->getDimensions())) + << output.DebugString(); + + std::vector output_data(6); + converter_.BuildAndRun("input", {1, 2, 3, 4, 5, 6}, "my_reshape", + &output_data); + EXPECT_THAT(output_data, ElementsAre(1, 2, 3, 4, 5, 6)); + } +} + +} // namespace convert +} // namespace tensorrt +} // namespace tensorflow + +#endif // GOOGLE_TENSORRT +#endif // GOOGLE_CUDA -- GitLab From 6687a0bcd059f8cefca7a669dcfb54b19423f07b Mon Sep 17 00:00:00 2001 From: Geoffrey Irving Date: Mon, 15 Oct 2018 21:33:33 -0700 Subject: [PATCH 0133/1825] Make api_compatibility_test pass in Python 3 This is a bunch of hacks, but it isn't that much overall code. I leave it up to others whether it's worth ignoring, cleaning up, or adopting with minimal changes. Main plus: nearly everyone outside Google uses Python 3, and this would make it easier for people to contribute. --- .../api/lib/python_object_to_proto_visitor.py | 65 +++++++++++++++++-- .../tools/api/tests/api_compatibility_test.py | 10 +-- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/tensorflow/tools/api/lib/python_object_to_proto_visitor.py b/tensorflow/tools/api/lib/python_object_to_proto_visitor.py index 3a48cf683c..bf67334836 100644 --- a/tensorflow/tools/api/lib/python_object_to_proto_visitor.py +++ b/tensorflow/tools/api/lib/python_object_to_proto_visitor.py @@ -19,6 +19,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import re +import sys from google.protobuf import message from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util import tf_decorator @@ -33,6 +35,59 @@ _CORNER_CASES = { } +# Python 2 vs. 3 differences +if sys.version_info.major == 3: + _CODE_ATTR = '__code__' + _CLASS_TO_TYPE = {} + for t in ('property', 'object', 'getset_descriptor', 'int', 'str', 'type', + 'tuple', 'module', 'collections.defaultdict', 'set', 'dict', + 'NoneType', 'frozenset'): + _CLASS_TO_TYPE["" % t] = "" % t + for e in 'Exception', 'RuntimeError': + _CLASS_TO_TYPE["" % e] = "" % e + _NORMALIZE_ISINSTANCE = { + "": + "", + "": + ""} + + def _normalize_type(ty): + return _CLASS_TO_TYPE.get(ty, ty) + + def _normalize_isinstance(ty): + return _NORMALIZE_ISINSTANCE.get(ty, ty) + + def _skip_member(cls, member): + if member == 'with_traceback': + return True + if (cls in ('VariableSynchronization', 'UnconnectedGradients', 'VariableAggregation') and + member in ('name', 'value')): + return True + + def normalize_proto(proto): + for kind in 'tf_module', 'tf_class': + if proto.HasField(kind): + for member in getattr(proto, kind).member: + if member.mtype == "": + member.mtype = "" + if proto.path == 'tensorflow.train.NanLossDuringTrainingError': + del proto.tf_class.member[1] +else: + _CODE_ATTR = 'func_code' + + def _normalize_type(ty): + return ty + + def _normalize_isinstance(ty): + return ty + + def _skip_member(cls, member): + return False + + def normalize_proto(proto): + pass + + def _SanitizedArgSpec(obj): """Get an ArgSpec string that is free of addresses. @@ -91,7 +146,7 @@ def _SanitizedMRO(obj): if cls.__name__ == '_NewClass': # Ignore class created by @deprecated_alias decorator. continue - str_repr = str(cls) + str_repr = _normalize_type(str(cls)) return_list.append(str_repr) if 'tensorflow' not in str_repr: break @@ -130,6 +185,8 @@ class PythonObjectToProtoVisitor(object): def _AddMember(member_name, member_obj, proto): """Add the child object to the object being constructed.""" _, member_obj = tf_decorator.unwrap(member_obj) + if _skip_member(parent.__name__, member_name): + return if member_name == '__init__' or not member_name.startswith('_'): if tf_inspect.isroutine(member_obj): new_method = proto.member_method.add() @@ -137,12 +194,12 @@ class PythonObjectToProtoVisitor(object): # If member_obj is a python builtin, there is no way to get its # argspec, because it is implemented on the C side. It also has no # func_code. - if getattr(member_obj, 'func_code', None): + if hasattr(member_obj, _CODE_ATTR): new_method.argspec = _SanitizedArgSpec(member_obj) else: new_member = proto.member.add() new_member.name = member_name - new_member.mtype = str(type(member_obj)) + new_member.mtype = _normalize_type(str(type(member_obj))) parent_corner_cases = _CORNER_CASES.get(path, {}) @@ -172,7 +229,7 @@ class PythonObjectToProtoVisitor(object): elif tf_inspect.isclass(parent): # Construct a class. class_obj = api_objects_pb2.TFAPIClass() - class_obj.is_instance.extend(_SanitizedMRO(parent)) + class_obj.is_instance.extend(_normalize_isinstance(i) for i in _SanitizedMRO(parent)) for name, child in children: if name in parent_corner_cases: # If we have an empty entry, skip this object. diff --git a/tensorflow/tools/api/tests/api_compatibility_test.py b/tensorflow/tools/api/tests/api_compatibility_test.py index 6487a6267e..f069ace5b1 100644 --- a/tensorflow/tools/api/tests/api_compatibility_test.py +++ b/tensorflow/tools/api/tests/api_compatibility_test.py @@ -299,6 +299,7 @@ class ApiCompatibilityTest(test.TestCase): """Read a filename, create a protobuf from its contents.""" ret_val = api_objects_pb2.TFAPIObject() text_format.Merge(file_io.read_file_to_string(filename), ret_val) + python_object_to_proto_visitor.normalize_proto(ret_val) return ret_val golden_proto_dict = { @@ -315,9 +316,6 @@ class ApiCompatibilityTest(test.TestCase): update_goldens=FLAGS.update_goldens, api_version=api_version) - @unittest.skipUnless( - sys.version_info.major == 2, - 'API compabitility test goldens are generated using python2.') def testAPIBackwardsCompatibility(self): api_version = 1 golden_file_pattern = os.path.join( @@ -331,9 +329,6 @@ class ApiCompatibilityTest(test.TestCase): # in separate tests. additional_private_map={'tf.compat': ['v1', 'v2']}) - @unittest.skipUnless( - sys.version_info.major == 2, - 'API compabitility test goldens are generated using python2.') def testAPIBackwardsCompatibilityV1(self): api_version = 1 golden_file_pattern = os.path.join( @@ -342,9 +337,6 @@ class ApiCompatibilityTest(test.TestCase): self._checkBackwardsCompatibility( tf_v2.compat.v1, golden_file_pattern, api_version) - @unittest.skipUnless( - sys.version_info.major == 2, - 'API compabitility test goldens are generated using python2.') def testAPIBackwardsCompatibilityV2(self): api_version = 2 golden_file_pattern = os.path.join( -- GitLab From 849910cb0525cf35a594ff8fb18f89477282de5a Mon Sep 17 00:00:00 2001 From: Chris Antaki Date: Mon, 15 Oct 2018 22:05:41 -0700 Subject: [PATCH 0134/1825] Update README.md --- tensorflow/contrib/tfprof/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/contrib/tfprof/README.md b/tensorflow/contrib/tfprof/README.md index 7faf2b9b24..e32c691fed 100644 --- a/tensorflow/contrib/tfprof/README.md +++ b/tensorflow/contrib/tfprof/README.md @@ -21,4 +21,3 @@ * Python API * Command Line * Visualization -* C++ API (Not public, contact us if needed.) -- GitLab From 231ef238b5e9047ce85ba30e340e09b1a21a585a Mon Sep 17 00:00:00 2001 From: Anton Dmitriev Date: Tue, 16 Oct 2018 14:24:41 +0300 Subject: [PATCH 0135/1825] Add ability to start TensorFlow server from Java API. --- tensorflow/c/BUILD | 1 + tensorflow/c/c_api.cc | 39 +++++++++ tensorflow/c/c_api.h | 23 ++++++ tensorflow/c/c_api_internal.h | 7 ++ .../src/main/java/org/tensorflow/Server.java | 82 +++++++++++++++++++ tensorflow/java/src/main/native/BUILD | 1 + tensorflow/java/src/main/native/server_jni.cc | 73 +++++++++++++++++ tensorflow/java/src/main/native/server_jni.h | 66 +++++++++++++++ 8 files changed, 292 insertions(+) create mode 100644 tensorflow/java/src/main/java/org/tensorflow/Server.java create mode 100644 tensorflow/java/src/main/native/server_jni.cc create mode 100644 tensorflow/java/src/main/native/server_jni.h diff --git a/tensorflow/c/BUILD b/tensorflow/c/BUILD index 17e2e292eb..ed9c94688d 100644 --- a/tensorflow/c/BUILD +++ b/tensorflow/c/BUILD @@ -94,6 +94,7 @@ tf_cuda_library( "//tensorflow/core:protos_all_cc", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core/distributed_runtime:server_lib", ], }) + select({ "//tensorflow:with_xla_support": [ diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc index 79811ceae5..6101c1b6af 100644 --- a/tensorflow/c/c_api.cc +++ b/tensorflow/c/c_api.cc @@ -2803,4 +2803,43 @@ TF_Buffer* TF_GetRegisteredKernelsForOp(const char* name, TF_Status* status) { } return ret; } + +// TF_Server functions ---------------------------------------------- + +TF_Server::TF_Server(tensorflow::ServerInterface* server) : server(server) {} + +TF_Server* TF_NewServer(const void* proto, size_t proto_len, + TF_Status* status) { + tensorflow::ServerDef server_def; + if (!server_def.ParseFromArray(proto, static_cast(proto_len))) { + status->status = InvalidArgument("Unparseable ServerDef"); + return nullptr; + } + + auto out_server = new std::unique_ptr(); + status->status = tensorflow::NewServer(server_def, out_server); + if (!status->status.ok()) return nullptr; + + return new TF_Server(out_server->release()); +} + +void TF_StartServer(TF_Server* server, TF_Status* status) { + status->status = server->server->Start(); +} + +void TF_StopServer(TF_Server* server, TF_Status* status) { + status->status = server->server->Stop(); +} + +void TF_JoinServer(TF_Server* server, TF_Status* status) { + status->status = server->server->Join(); +} + +void TF_DeleteServer(TF_Server* server) { + if (server != nullptr) { + if (server->server != nullptr) delete server->server; + delete server; + } +} + } // end extern "C" diff --git a/tensorflow/c/c_api.h b/tensorflow/c/c_api.h index 850f6ecd63..bb5741e73d 100644 --- a/tensorflow/c/c_api.h +++ b/tensorflow/c/c_api.h @@ -1662,6 +1662,29 @@ TF_CAPI_EXPORT extern TF_Buffer* TF_GetAllRegisteredKernels(TF_Status* status); TF_CAPI_EXPORT extern TF_Buffer* TF_GetRegisteredKernelsForOp( const char* name, TF_Status* status); +// -------------------------------------------------------------------------- +// Server functionality. + +// Server. +typedef struct TF_Server TF_Server; + +// Creates new server. +TF_CAPI_EXPORT extern TF_Server* TF_NewServer(const void* proto, + size_t proto_len, + TF_Status* status); + +// Starts a server. +TF_CAPI_EXPORT extern void TF_StartServer(TF_Server* server, TF_Status* status); + +// Stops a server. +TF_CAPI_EXPORT extern void TF_StopServer(TF_Server* server, TF_Status* status); + +// Blocks until the server has shut down (currently blocks forever). +TF_CAPI_EXPORT extern void TF_JoinServer(TF_Server* server, TF_Status* status); + +// Destroy a server, frees memory. +TF_CAPI_EXPORT extern void TF_DeleteServer(TF_Server* server); + #ifdef __cplusplus } /* end extern "C" */ #endif diff --git a/tensorflow/c/c_api_internal.h b/tensorflow/c/c_api_internal.h index 95652a1137..59c8a2b7c7 100644 --- a/tensorflow/c/c_api_internal.h +++ b/tensorflow/c/c_api_internal.h @@ -37,6 +37,7 @@ limitations under the License. #include "tensorflow/core/platform/mutex.h" #include "tensorflow/core/platform/types.h" #include "tensorflow/core/public/session.h" +#include "tensorflow/core/distributed_runtime/server_lib.h" namespace tensorflow { class Device; @@ -179,6 +180,12 @@ struct TF_ApiDefMap { tensorflow::mutex lock; }; +struct TF_Server { + TF_Server(tensorflow::ServerInterface* server); + + tensorflow::ServerInterface* server; +}; + namespace tensorflow { class TensorCApi { diff --git a/tensorflow/java/src/main/java/org/tensorflow/Server.java b/tensorflow/java/src/main/java/org/tensorflow/Server.java new file mode 100644 index 0000000000..18ee99e00a --- /dev/null +++ b/tensorflow/java/src/main/java/org/tensorflow/Server.java @@ -0,0 +1,82 @@ +/* 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. +==============================================================================*/ + +package org.tensorflow; + +/** + * An in-process TensorFlow server, for use in distributed training. + * + * A {@code tf.train.Server} instance encapsulates a set of devices and a + * {@code tf.Session} target that can participate in distributed training. A + * server belongs to a cluster (specified by a {@code tf.train.ClusterSpec}), + * and corresponds to a particular task in a named job. The server can + * communicate with any other server in the same cluster. + * + *

WARNING:A {@code Server} owns resources that must be + * explicitly freed by invoking {@link #close()}. + * + *

Instances of a {@code Server} are thread-safe. + */ +public final class Server implements AutoCloseable { + + /** + * Constructs a new instance of server. + * + * @param config Server definition specified as a serialized + * ServerDef + * protocol buffer. + */ + public Server(byte[] serverDef) { + nativeHandle = allocate(serverDef); + } + + /** Starts this server. */ + public synchronized void start() { + start(nativeHandle); + } + + /** Stops this server. */ + public synchronized void stop() { + stop(nativeHandle); + } + + /** Blocks until the server has shut down (currently blocks forever). */ + public synchronized void join() { + join(nativeHandle); + } + + @Override + public void close() { + delete(nativeHandle); + + nativeHandle = 0; + } + + private static native long allocate(byte[] serverDef); + + private static native void start(long nativeHandle); + + private static native void stop(long nativeHandle); + + private static native void join(long nativeHandle); + + private static native void delete(long nativeHandle); + + private long nativeHandle; + + static { + TensorFlow.init(); + } +} \ No newline at end of file diff --git a/tensorflow/java/src/main/native/BUILD b/tensorflow/java/src/main/native/BUILD index 49348daa94..530224aa94 100644 --- a/tensorflow/java/src/main/native/BUILD +++ b/tensorflow/java/src/main/native/BUILD @@ -43,6 +43,7 @@ tf_cuda_library( "//tensorflow/core:all_kernels", "//tensorflow/core:direct_session", "//tensorflow/core:ops", + "//tensorflow/core/distributed_runtime/rpc:grpc_server_lib", ], }), alwayslink = 1, diff --git a/tensorflow/java/src/main/native/server_jni.cc b/tensorflow/java/src/main/native/server_jni.cc new file mode 100644 index 0000000000..7eca920230 --- /dev/null +++ b/tensorflow/java/src/main/native/server_jni.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. +==============================================================================*/ + +#include "tensorflow/java/src/main/native/server_jni.h" +#include "tensorflow/c/c_api.h" +#include "tensorflow/java/src/main/native/exception_jni.h" +#include "tensorflow/java/src/main/native/utils_jni.h" + +JNIEXPORT jlong JNICALL Java_org_tensorflow_Server_allocate( + JNIEnv* env, jclass clazz, jbyteArray server_def) { + TF_Status* status = TF_NewStatus(); + + jbyte* server_def_ptr = env->GetByteArrayElements(server_def, nullptr); + + TF_Server* server = TF_NewServer( + server_def_ptr, static_cast(env->GetArrayLength(server_def)), + status); + + env->ReleaseByteArrayElements(server_def, server_def_ptr, JNI_ABORT); + throwExceptionIfNotOK(env, status); + + return reinterpret_cast(server); +} + +JNIEXPORT void JNICALL Java_org_tensorflow_Server_start(JNIEnv* env, + jclass clazz, + jlong handle) { + TF_Status* status = TF_NewStatus(); + TF_Server* server = reinterpret_cast(handle); + + TF_StartServer(server, status); + throwExceptionIfNotOK(env, status); +} + +JNIEXPORT void JNICALL Java_org_tensorflow_Server_stop(JNIEnv* env, + jclass clazz, + jlong handle) { + TF_Status* status = TF_NewStatus(); + TF_Server* server = reinterpret_cast(handle); + + TF_StopServer(server, status); + throwExceptionIfNotOK(env, status); +} + +JNIEXPORT void JNICALL Java_org_tensorflow_Server_join(JNIEnv* env, + jclass clazz, + jlong handle) { + TF_Status* status = TF_NewStatus(); + TF_Server* server = reinterpret_cast(handle); + + TF_JoinServer(server, status); + throwExceptionIfNotOK(env, status); +} + +JNIEXPORT void JNICALL Java_org_tensorflow_Server_delete(JNIEnv* env, + jclass clazz, + jlong handle) { + TF_Server* server = reinterpret_cast(handle); + + TF_DeleteServer(server); +} diff --git a/tensorflow/java/src/main/native/server_jni.h b/tensorflow/java/src/main/native/server_jni.h new file mode 100644 index 0000000000..4bfe90b7a8 --- /dev/null +++ b/tensorflow/java/src/main/native/server_jni.h @@ -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. +==============================================================================*/ + +#ifndef TENSORFLOW_JAVA_SRC_MAIN_NATIVE_SERVER_JNI_H_ +#define TENSORFLOW_JAVA_SRC_MAIN_NATIVE_SERVER_JNI_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Class: org_tensorflow_Server + * Method: allocate + * Signature: ([B)J + */ +JNIEXPORT jlong JNICALL +Java_org_tensorflow_Server_allocate(JNIEnv *, jclass, jbyteArray server_def); + +/* + * Class: org_tensorflow_Server + * Method: start + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_tensorflow_Server_start(JNIEnv *, jclass, + jlong); + +/* + * Class: org_tensorflow_Server + * Method: stop + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_tensorflow_Server_stop(JNIEnv *, jclass, jlong); + +/* + * Class: org_tensorflow_Session + * Method: join + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_tensorflow_Server_join(JNIEnv *, jclass, jlong); + +/* + * Class: org_tensorflow_Session + * Method: delete + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_org_tensorflow_Server_delete(JNIEnv *, jclass, + jlong); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus +#endif // TENSORFLOW_JAVA_SRC_MAIN_NATIVE_SERVER_JNI_H_ -- GitLab From 15ded16f1e881c0196ac0afea93ed8d6c46cee26 Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Tue, 16 Oct 2018 13:34:42 +0100 Subject: [PATCH 0136/1825] Explicitly say in comments that new function only updates execution device if it has not yet specified in graf_def --- tensorflow/c/c_api.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/c/c_api.h b/tensorflow/c/c_api.h index c93a4d226c..b741bac546 100644 --- a/tensorflow/c/c_api.h +++ b/tensorflow/c/c_api.h @@ -900,7 +900,9 @@ TF_CAPI_EXPORT extern void TF_DeleteImportGraphDefOptions( TF_CAPI_EXPORT extern void TF_ImportGraphDefOptionsSetPrefix( TF_ImportGraphDefOptions* opts, const char* prefix); -// Set default execution device for the nodes in the `graph_def` that will be imported into `graph`. +// Set default execution device for the nodes in the `graph_def` +// if it has not been explicitly specified for operation already, +// it will be imported into `graph` then. // `device` is copied and has no lifetime requirements. TF_CAPI_EXPORT extern void TF_ImportGraphDefOptionsSetDefaultDevice( TF_ImportGraphDefOptions* opts, const char* device); -- GitLab From 5f741287b98c5584faf1408e4f486e86ef521b1c Mon Sep 17 00:00:00 2001 From: Doe Hyun Yoon Date: Tue, 16 Oct 2018 08:00:33 -0700 Subject: [PATCH 0137/1825] Propagate Tensor values in Grappler static shape inference. Currently, GraphProperties.InferStatically() propagates outputs (shape) and output_tensors_as_shape (value in shape). Tensor values (input_tensors in InferenceContext) are fed only for a few cases: Const, Rank, and Size ops as input. But they don't propagate; e.g., X has input_tensor value for Const -> X, but not for this case: Const -> Identity -> X. This CL implements the followings: (1) add input_tensor_protos and output_tensor_protos in the NodeContext struct in SymbolicShapeRefiner (2) made MaybeUpdateNodeContextOutput() and all the ad-hoc NodeContext output setting in InferShapes() into the method (it makes more sense to do that ad-hoc setting of NodeContext output after we run shape inference function that sets output (shape) of a node (3) moved ad-hoc setting of input tensor values (for Const, Rank, and Size ops) in UpdateNode() to MaybeUpdateNodeContextOutput(); all the ad-hoc configs are in the MaybeUpdateNodeContextOutput(), and we simply propagates input node's output_tensor_protos to input_tensor_protos. (4) Other minor clean up and tests. With this CL, we can set input_tensors in InferenceContext in many more cases; for example, ops like Split needs input_tensors, not input_tensors_as_shape. Besides, we can later add more adhoc logic to propagate tensor value easily. PiperOrigin-RevId: 217317682 --- tensorflow/core/grappler/costs/BUILD | 1 + .../core/grappler/costs/graph_properties.cc | 289 +++++++++++++----- .../grappler/costs/graph_properties_test.cc | 83 ++++- 3 files changed, 296 insertions(+), 77 deletions(-) diff --git a/tensorflow/core/grappler/costs/BUILD b/tensorflow/core/grappler/costs/BUILD index 01e8f2b185..144d7f8ce6 100644 --- a/tensorflow/core/grappler/costs/BUILD +++ b/tensorflow/core/grappler/costs/BUILD @@ -41,6 +41,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ ":utils", + "@com_google_absl//absl/memory", "//tensorflow/core/grappler/utils:functions", "//tensorflow/core/grappler/utils:topological_sort", "//tensorflow/core/grappler:graph_view", diff --git a/tensorflow/core/grappler/costs/graph_properties.cc b/tensorflow/core/grappler/costs/graph_properties.cc index 56c8339d57..dd6ce0c132 100644 --- a/tensorflow/core/grappler/costs/graph_properties.cc +++ b/tensorflow/core/grappler/costs/graph_properties.cc @@ -15,14 +15,18 @@ limitations under the License. #include "tensorflow/core/grappler/costs/graph_properties.h" +#include +#include #include #include #include +#include "absl/memory/memory.h" #include "tensorflow/core/framework/common_shape_fns.h" #include "tensorflow/core/framework/function.pb.h" #include "tensorflow/core/framework/node_def_util.h" #include "tensorflow/core/framework/tensor.pb.h" #include "tensorflow/core/framework/tensor_shape.pb.h" +#include "tensorflow/core/framework/types.pb.h" #include "tensorflow/core/framework/versions.pb.h" #include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/grappler/costs/utils.h" @@ -259,6 +263,8 @@ typename DisjointSet::Rep* DisjointSet::Find(Handle value) { return root; } +// TODO(dyoon): Move many helper functions in this file (including those within +// SymbolicShapeRefiner class) to shared utils. bool IsEnqueue(const NodeDef& n) { return (n.op().find("Enqueue") != string::npos && n.op().find("EnqueueMany") == string::npos); @@ -380,21 +386,29 @@ TensorProto MakeTensorProtoFromShape(InferenceContext* ic, return tensor_proto; } -// Returns a Const NodeDef with shape = `shape`, values = `tensor_as_shape`, -// and dtype = `dtype`. -NodeDef MakeConstNodeDefFromShape(InferenceContext* ic, - const ShapeHandle& shape, - const ShapeHandle& tensor_as_shape, - const DataType& dtype) { +// Returns a Const NodeDef with tensor `tensor_proto` and dtype = `dtype`. +NodeDef MakeConstNodeDefFromTensorProto(InferenceContext* ic, + const TensorProto& tensor_proto, + const DataType& dtype) { NodeDef const_node; const_node.set_name("const_from_shape"); const_node.set_op("Const"); auto* attr = const_node.mutable_attr(); (*attr)["dtype"].set_type(dtype); auto* tensor = (*attr)["value"].mutable_tensor(); - *tensor = MakeTensorProtoFromShape(ic, shape, tensor_as_shape, dtype); + *tensor = tensor_proto; return const_node; } + +// Returns a Const NodeDef with shape = `shape`, values = `tensor_as_shape`, +// and dtype = `dtype`. +NodeDef MakeConstNodeDefFromShape(InferenceContext* ic, + const ShapeHandle& shape, + const ShapeHandle& tensor_as_shape, + const DataType& dtype) { + return MakeConstNodeDefFromTensorProto( + ic, MakeTensorProtoFromShape(ic, shape, tensor_as_shape, dtype), dtype); +} } // namespace // Queue of nodes to process. Nodes can be enqueued in any order, but will be @@ -455,6 +469,9 @@ class SymbolicShapeRefiner { DataTypeVector input_types; DataTypeVector output_types; std::unique_ptr inference_context; + // Additional info for propagating tensor values and tensor shapes. + std::vector input_tensor_protos; + std::vector output_tensor_protos; std::vector output_tensors_as_shapes; }; @@ -553,6 +570,12 @@ class SymbolicShapeRefiner { if (IsConstant(*input_node)) { TF_CHECK_OK( ReplaceInputWithConst(*input_node, i, &grappler_function_item)); + } else if (ctx->input_tensor_protos.size() > i && + ctx->input_tensor_protos[i] != nullptr) { + NodeDef const_input_node = MakeConstNodeDefFromTensorProto( + ic, *ctx->input_tensor_protos[i], ctx->input_types[i]); + TF_CHECK_OK(ReplaceInputWithConst(const_input_node, i, + &grappler_function_item)); } else if (ic->input_tensors_as_shapes().size() > i && IsShapeFullyDefinedIntegerVectorOrScalar( ic, ic->input(i), ic->input_tensors_as_shapes()[i], @@ -574,6 +597,8 @@ class SymbolicShapeRefiner { // Add return nodes for output shapes. int output = 0; ctx->output_tensors_as_shapes.resize(grappler_function_item.output_size()); + 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) { // TODO(jmdecker): Handle case of multiple output tensors @@ -610,8 +635,11 @@ class SymbolicShapeRefiner { // Forward tensor value to output_tensors_as_shape. Tensor tensor; if (tensor.FromProto(outprop.value())) { - MaybeSetTensorValueToShape(ic, tensor, - &ctx->output_tensors_as_shapes[output]); + MaybeTensorValueToShape(ic, tensor, + &ctx->output_tensors_as_shapes[output]); + const_tensors_to_propagate_.push_back(outprop.value()); + ctx->output_tensor_protos[output] = + &const_tensors_to_propagate_.back(); } } output++; @@ -636,6 +664,8 @@ class SymbolicShapeRefiner { nullptr); std::vector input_tensors_as_shapes( inference_context->num_inputs()); + node_context->input_tensor_protos.resize(inference_context->num_inputs(), + nullptr); for (int dst_input = 0; dst_input < inference_context->num_inputs(); ++dst_input) { @@ -651,55 +681,59 @@ class SymbolicShapeRefiner { "' was not previously added to SymbolicShapeRefiner."); } - if (IsConstant(*input)) { - // Convert constant value into tensors. - if (const_values[dst_input].FromProto( - input->attr().at("value").tensor())) { - input_tensors[dst_input] = &const_values[dst_input]; - MaybeSetTensorValueToShape(inference_context, - const_values[dst_input], - &input_tensors_as_shapes[dst_input]); - } - } else if (IsRank(*input)) { - if (c->inference_context->RankKnown(c->inference_context->input(0))) { - int32 rank = - c->inference_context->Rank(c->inference_context->input(0)); - Tensor t(DT_INT32, {}); - t.flat()(0) = rank; - const_values[dst_input] = t; - input_tensors[dst_input] = &const_values[dst_input]; - } - } else if (IsSize(*input)) { - DimensionHandle size = - c->inference_context->NumElements(c->inference_context->input(0)); - if (c->inference_context->ValueKnown(size)) { - int64 sz = c->inference_context->Value(size); - bool valid = false; - if (input->attr().at("T").type() == DT_INT32) { - if (sz < std::numeric_limits::max()) { - Tensor t(DT_INT32, {}); - t.flat()(0) = sz; - const_values[dst_input] = t; - valid = true; - } - } else { - Tensor t(DT_INT64, {}); - t.flat()(0) = sz; - const_values[dst_input] = t; - valid = true; - } - if (valid) { - input_tensors[dst_input] = &const_values[dst_input]; - } - } - } + // Propagate input node's NodeContext info to the current node's + // NodeContext: + // output_tensor_protos to input_tensor_protos and input_tensors, and + // output_tensors_as_shapes to input_tensors_as_shapes. if (c->output_tensors_as_shapes.size() > src_output) { input_tensors_as_shapes[dst_input] = c->output_tensors_as_shapes[src_output]; } + if (c->output_tensor_protos.size() > src_output) { + auto* tensor_proto = c->output_tensor_protos[src_output]; + if (tensor_proto != nullptr && + const_values[dst_input].FromProto(*tensor_proto)) { + input_tensors[dst_input] = &const_values[dst_input]; + node_context->input_tensor_protos[dst_input] = tensor_proto; + + if (!inference_context->FullyDefined( + input_tensors_as_shapes[dst_input])) { + // Shape from a Const is not fully defined when the Const has + // value -1 (e.g., Reshape(x, Const(-1)) to reshape an arbitrary + // tensor x to a vector). + // It's possible that the same Const with -1 is used in many + // places, but that doesn't mean the resultant shapes are + // identical. e.g., x1 = Reshape(x, c) and y1 = Reshape(y, c), + // where c is -1. In this case, shape inference yields both x1 and + // y1 as rank 1, size unknown, but still the shapes of x1 and y1 + // can be different. (even if we use different Const(-1) for x1 + // and x2, graph optimzier may merge them to single Const through + // duplicate removal.) + // If we reuse output_tensors_as_shapes to input_tensors_as_shapes + // by copying ShapeHandle, they share the same Shape object, and + // SymbolicShapeManager, later in InferStatically(), assigns the + // same symbolic dim value (unique value < -1); in the above + // Reshape example, the shapes of x1 and y1 become, for example, + // [-278] and graph optimizer may yield incorrect output 'cause it + // assumes x1 and y1 have the same shape. + // To prevent this, we re-create a ShapeHandle from the Const + // tensor, instead of reusing output_tensors_as_shapes (so that + // ShapeHandles of the const fanouts have the same values, + // but different Shape objects -- SymbolicShapeManager assigns + // different symbol id to each fanout shape). + // TODO(dyoon): clean up the way values are propagated. + MaybeTensorValueToShape(inference_context, + const_values[dst_input], + &input_tensors_as_shapes[dst_input]); + } + } + } + DCHECK_GE(dst_input, 0); + // NOTE: we check only shape is refined; we do not (yet) check whether + // tensor value is refined. if (!*refined && !inference_context->input(dst_input).SameHandle( c->inference_context->output(src_output))) { *refined = true; @@ -974,17 +1008,53 @@ class SymbolicShapeRefiner { return dim; } - Status InferShapes(const NodeDef& node, NodeContext* c) { - InferenceContext* ic = c->inference_context.get(); - - auto it = fed_ports_.find(node.name()); - const bool is_fed = it != fed_ports_.end(); - - // Propagate shape tensors unless the node is fed. + Status MaybeUpdateNodeContextOutput(const NodeDef& node, const bool is_fed, + NodeContext* c) { + // Propagate tensors and shape tensors unless the node is fed. // TODO(bsteiner) We should still propagate the shapes to the ports that // aren't fed in the case of a ShapeN node. + + InferenceContext* ic = c->inference_context.get(); if (!is_fed) { - if (IsShape(node)) { + if (IsConstant(node)) { + c->output_tensor_protos.resize(1); + const TensorProto& tensor_proto = node.attr().at("value").tensor(); + c->output_tensor_protos[0] = &tensor_proto; + c->output_tensors_as_shapes.resize(1); + MaybeTensorProtoToShape(ic, tensor_proto, + &c->output_tensors_as_shapes[0]); + } else if (IsRank(node)) { + if (ic->RankKnown(ic->input(0))) { + // Propagate rank value. + int32 rank = ic->Rank(ic->input(0)); + const_tensors_to_propagate_.push_back( + MakeIntegerScalarTensorProto(DT_INT32, rank)); + c->output_tensor_protos.resize(1); + c->output_tensor_protos[0] = &const_tensors_to_propagate_.back(); + } + } else if (IsSize(node)) { + DimensionHandle size = ic->NumElements(ic->input(0)); + if (ic->ValueKnown(size)) { + // Propagate size value. + int64 sz = ic->Value(size); + bool valid = false; + if (node.attr().at("T").type() == DT_INT32) { + if (sz < std::numeric_limits::max()) { + const_tensors_to_propagate_.push_back( + MakeIntegerScalarTensorProto(DT_INT32, sz)); + valid = true; + } + } else { + const_tensors_to_propagate_.push_back( + MakeIntegerScalarTensorProto(DT_INT64, sz)); + valid = true; + } + if (valid) { + c->output_tensor_protos.resize(1); + c->output_tensor_protos[0] = &const_tensors_to_propagate_.back(); + } + } + } else if (IsShape(node)) { c->output_tensors_as_shapes.resize(1); c->output_tensors_as_shapes[0] = c->inference_context->input(0); } else if (IsShapeN(node)) { @@ -1042,9 +1112,12 @@ class SymbolicShapeRefiner { c->output_tensors_as_shapes[0] = ic->MakeShape(dims); } } else if (IsIdentity(node)) { - // Pass input_tensors_as_shapes to output_tensors_as_shapes. c->output_tensors_as_shapes.resize(1); c->output_tensors_as_shapes[0] = ic->input_tensors_as_shapes()[0]; + if (c->input_tensor_protos[0] != nullptr) { + c->output_tensor_protos.resize(1); + c->output_tensor_protos[0] = c->input_tensor_protos[0]; + } } else if (IsSlice(node)) { ShapeHandle input = ic->input_tensors_as_shapes()[0]; bool valid = ic->RankKnown(input); @@ -1125,7 +1198,10 @@ class SymbolicShapeRefiner { } } } + return Status::OK(); + } + Status InferShapes(const NodeDef& node, NodeContext* c) { // Infer the shapes of output tensors. if (!c->op_data || c->op_data->shape_inference_fn == nullptr) { // There is nothing more we can infer, annotate outputs with unknown @@ -1137,6 +1213,8 @@ class SymbolicShapeRefiner { c->inference_context->Run(c->op_data->shape_inference_fn)); Status status = Status::OK(); + auto it = fed_ports_.find(node.name()); + const bool is_fed = it != fed_ports_.end(); if (is_fed) { // It is possible to feed node output ports with tensors of any shape: as // a result, the shape of a fed port is completely unknown. @@ -1145,6 +1223,9 @@ class SymbolicShapeRefiner { } } + // Update NodeContext output fields after shape inference function runs. + status.Update(MaybeUpdateNodeContextOutput(node, is_fed, c)); + return status; } @@ -1166,17 +1247,65 @@ class SymbolicShapeRefiner { return false; } - void MaybeSetTensorValueToShape(InferenceContext* ic, const Tensor& tensor, - ShapeHandle* tensors_as_shapes) { + TensorProto MakeIntegerScalarTensorProto(const DataType dtype, + const int64 val) { + TensorProto tensor_proto; + tensor_proto.set_dtype(dtype); + // Scalar TensorProto has an empty tensor_shape; no dim, no dim.size. + tensor_proto.mutable_tensor_shape(); + if (dtype == DT_INT32) { + tensor_proto.add_int_val(val); + } else if (dtype == DT_INT64) { + tensor_proto.add_int64_val(val); + } + return tensor_proto; + } + + bool MaybeTensorProtoToShape(InferenceContext* ic, + const TensorProto& tensor_proto, + ShapeHandle* tensors_as_shapes) { + // Skip if dtype is not integer. + if (tensor_proto.dtype() != DT_INT32 && tensor_proto.dtype() != DT_INT64) { + return false; + } + // Skip if shape is neither scalar nor vector. + if (tensor_proto.tensor_shape().unknown_rank() || + tensor_proto.tensor_shape().dim_size() > 1) { + return false; + } + Tensor tensor; + if (!tensor.FromProto(tensor_proto)) { + return false; + } + return MaybeTensorValueToShape(ic, tensor, tensors_as_shapes); + } + + bool MaybeTensorValueToShape(InferenceContext* ic, const Tensor& tensor, + ShapeHandle* tensors_as_shapes) { // Integer tensors of rank one can also be interpreted as a shape // provided all their values are >= -1. if (IsIntegerVector(tensor)) { +#if 0 ShapeHandle tensor_shape = ic->Vector(tensor.NumElements()); ShapeHandle shp; // Note that MakeShapeFromTensor filters out invalid values (e.g., < -1). if (ic->MakeShapeFromTensor(&tensor, tensor_shape, &shp).ok()) { *tensors_as_shapes = shp; + return true; + } +#else + bool has_values_smaller_than_minus_1 = false; + std::vector dims; + for (int i = 0; i < tensor.NumElements(); i++) { + int64 value = tensor.dtype() == DT_INT32 ? tensor.flat()(i) + : tensor.flat()(i); + has_values_smaller_than_minus_1 |= (value < -1); + dims.push_back(value < 0 ? ic->UnknownDim() : ic->MakeDim(value)); } + if (!has_values_smaller_than_minus_1) { + *tensors_as_shapes = ic->MakeShape(dims); + } +#endif } else if (IsIntegerScalar(tensor)) { // Scalar constant. int64 value = tensor.dtype() == DT_INT32 ? tensor.flat()(0) @@ -1185,8 +1314,10 @@ class SymbolicShapeRefiner { // It's a limitation as we use ShapeHandle as a means to pass values. if (value >= -1) { *tensors_as_shapes = ic->MakeShape({ic->MakeDim(value)}); + return true; } } + return false; } const GraphView& graph_; @@ -1198,6 +1329,11 @@ class SymbolicShapeRefiner { fun_to_grappler_function_item_; FunctionLibraryDefinition function_library_; const std::unordered_map>& fed_ports_; + // Store TensorProtos for tensor value propagation. Note that we use list, not + // vector, as we use pointers to the TensorProtos in this container. Vector + // may resize and copy the objects into a new buffer, then the existing + // pointers become dangling pointers. + std::list const_tensors_to_propagate_; }; // Keep track of shapes and dimensions in a graph. @@ -1624,7 +1760,8 @@ Status GraphProperties::InferStatically(bool assume_valid_feeds) { PropagateShapes(&refiner, &new_shapes, resource_handles, num_loops)); // Track shapes globally across the graph. - SymbolicShapeManager shape_manager; + std::unique_ptr shape_manager = + absl::make_unique(); bool found_error = false; for (const NodeDef& node : item_.graph.node()) { auto node_ctx = refiner.GetContext(&node); @@ -1637,14 +1774,14 @@ Status GraphProperties::InferStatically(bool assume_valid_feeds) { continue; } for (const auto& merged_shapes : node_ctx->MergedShapes()) { - if (!shape_manager.Merge(merged_shapes.first, merged_shapes.second) + if (!shape_manager->Merge(merged_shapes.first, merged_shapes.second) .ok()) { found_error = true; break; } } for (const auto& merged_dims : node_ctx->MergedDims()) { - if (!shape_manager.Merge(merged_dims.first, merged_dims.second).ok()) { + if (!shape_manager->Merge(merged_dims.first, merged_dims.second).ok()) { found_error = true; break; } @@ -1652,7 +1789,7 @@ Status GraphProperties::InferStatically(bool assume_valid_feeds) { if (found_error) { // The shapes aren't consistent, we can't infer safely: discard all the // information discovered so far. - shape_manager = SymbolicShapeManager(); + shape_manager = absl::make_unique(); break; } } @@ -1676,15 +1813,17 @@ Status GraphProperties::InferStatically(bool assume_valid_feeds) { input_properties.resize(ic->num_inputs()); GraphView::InputPort input(&node, -1); for (int i = 0; i < ic->num_inputs(); ++i) { - shape_manager.AsTensorProperties(ic->input(i), ctx->input_types[i], - &input_properties[i]); + shape_manager->AsTensorProperties(ic->input(i), ctx->input_types[i], + &input_properties[i]); input.port_id = i; GraphView::OutputPort fanin = graph_view.GetRegularFanin(input); - // Export tensor value (either const tensor or input_tensors_as_shapes) - // to input_properties.value. + // Export tensor value to input_properties.value. if (IsConstant(*fanin.node)) { const TensorProto& raw_val = fanin.node->attr().at("value").tensor(); *input_properties[i].mutable_value() = raw_val; + } else if (ctx->input_tensor_protos.size() > i && + ctx->input_tensor_protos[i] != nullptr) { + *input_properties[i].mutable_value() = *ctx->input_tensor_protos[i]; } else if (ic->input_tensors_as_shapes().size() > i && IsShapeFullyDefinedIntegerVectorOrScalar( ic, ic->input(i), ic->input_tensors_as_shapes()[i], @@ -1705,13 +1844,15 @@ Status GraphProperties::InferStatically(bool assume_valid_feeds) { output_properties.resize(ic->num_outputs()); for (int i = 0; i < ic->num_outputs(); ++i) { - shape_manager.AsTensorProperties(ic->output(i), ctx->output_types[i], - &output_properties[i]); - // Export tensor value (either const tensor or input_tensors_as_shapes) - // to output_properties.value. + shape_manager->AsTensorProperties(ic->output(i), ctx->output_types[i], + &output_properties[i]); + // Export tensor value to output_properties.value. if (IsConstant(node)) { const TensorProto& raw_val = node.attr().at("value").tensor(); *output_properties[i].mutable_value() = raw_val; + } else if (ctx->output_tensor_protos.size() > i && + ctx->output_tensor_protos[i] != nullptr) { + *output_properties[i].mutable_value() = *ctx->output_tensor_protos[i]; } else if (ctx->output_tensors_as_shapes.size() > i && IsShapeFullyDefinedIntegerVectorOrScalar( ic, ic->output(i), ctx->output_tensors_as_shapes[i], diff --git a/tensorflow/core/grappler/costs/graph_properties_test.cc b/tensorflow/core/grappler/costs/graph_properties_test.cc index db10f586bc..5aae773994 100644 --- a/tensorflow/core/grappler/costs/graph_properties_test.cc +++ b/tensorflow/core/grappler/costs/graph_properties_test.cc @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/core/framework/tensor.pb.h" // NOLINT #include "tensorflow/core/framework/tensor_shape.pb.h" #include "tensorflow/core/framework/tensor_testutil.h" +#include "tensorflow/core/framework/types.pb.h" #include "tensorflow/core/framework/versions.pb.h" #include "tensorflow/core/grappler/clusters/single_machine.h" #include "tensorflow/core/grappler/grappler_item.h" @@ -285,6 +286,37 @@ TEST_F(GraphPropertiesTest, Variables) { } } +TEST_F(GraphPropertiesTest, ReadVariableOpAfterEnter) { + GrapplerItem item; + TF_CHECK_OK(NodeDefBuilder("Var", "VarHandleOp") + .Attr("dtype", DT_FLOAT) + .Attr("shape", TensorShape({3, 7})) + .Finalize(item.graph.add_node())); + TF_CHECK_OK(NodeDefBuilder("Enter", "Enter") + .Attr("T", DT_RESOURCE) + .Attr("frame_name", "while_context") + .Attr("is_constant", true) + .Attr("parallel_iterations", 10) + .Input("Var", 0, DT_RESOURCE) + .Finalize(item.graph.add_node())); + TF_CHECK_OK(NodeDefBuilder("ReadVariableOpAfterEnter", "ReadVariableOp") + .Attr("dtype", DT_FLOAT) + .Input("Enter", 0, DT_RESOURCE) + .Finalize(item.graph.add_node())); + + // LOG(INFO) << item.graph.DebugString(); + GraphProperties properties(item); + TF_CHECK_OK(properties.InferStatically(false)); + const auto props = properties.GetOutputProperties("ReadVariableOpAfterEnter"); + EXPECT_EQ(1, props.size()); + const OpInfo::TensorProperties& prop = props[0]; + EXPECT_EQ(DT_FLOAT, prop.dtype()); + EXPECT_FALSE(prop.shape().unknown_rank()); + EXPECT_EQ(2, prop.shape().dim_size()); + EXPECT_EQ(3, prop.shape().dim(0).size()); + EXPECT_EQ(7, prop.shape().dim(1).size()); +} + TEST_F(GraphPropertiesTest, VarHandles) { GrapplerItem item; TF_CHECK_OK(NodeDefBuilder("Var", "VarHandleOp") @@ -865,8 +897,8 @@ TEST_F(GraphPropertiesTest, TensorAsShapesPropagation) { EXPECT_TRUE(properties.GetOutputProperties("b1")[0].has_value()); EXPECT_TRUE(properties.GetOutputProperties("c")[0].has_value()); EXPECT_TRUE(properties.GetInputProperties("c1")[0].has_value()); - // Note that we propagate tensro value of only 1D vector and scalar. - EXPECT_FALSE(properties.GetOutputProperties("c1")[0].has_value()); + // Note that we propagate tensor value of only 1D vector and scalar. + EXPECT_TRUE(properties.GetOutputProperties("c1")[0].has_value()); // Check values. ExpectTensorValues({5, 7}, properties.GetOutputProperties("a")[0].value()); @@ -883,7 +915,8 @@ TEST_F(GraphPropertiesTest, TensorAsShapesPropagation) { properties.GetOutputProperties("c")[0].value()); ExpectTensorValues({c_values}, properties.GetInputProperties("c1")[0].value()); - // No output value for c1, as it's neither 1D vector nor scalar. + ExpectTensorValues({c_values}, + properties.GetOutputProperties("c1")[0].value()); } TEST_F(GraphPropertiesTest, IdentityPassingShape) { @@ -928,6 +961,50 @@ TEST_F(GraphPropertiesTest, PackWithConstInput) { EXPECT_EQ("float: [1,2,3,4]", PropToString(out_prop0)); } +TEST_F(GraphPropertiesTest, RankOp) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output c = ops::Const(s.WithOpName("Const"), 1, {4, 4, 4}); + Output r = ops::Rank(s.WithOpName("Rank"), c); + Output i = ops::Identity(s.WithOpName("Identity"), r); + + GrapplerItem item; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + GraphProperties properties(item); + TF_CHECK_OK(properties.InferStatically(false)); + const auto rank_props = properties.GetOutputProperties("Rank"); + const OpInfo::TensorProperties rank_prop0 = rank_props[0]; + EXPECT_EQ("int32: []", PropToString(rank_prop0)); + EXPECT_TRUE(rank_prop0.has_value()); + ExpectTensorValues({3}, rank_prop0.value()); + const auto identity_props = properties.GetOutputProperties("Identity"); + const OpInfo::TensorProperties identity_props0 = identity_props[0]; + EXPECT_EQ("int32: []", PropToString(identity_props0)); + EXPECT_TRUE(identity_props0.has_value()); + ExpectTensorValues({3}, identity_props0.value()); +} + +TEST_F(GraphPropertiesTest, SizeOp) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output c = ops::Const(s.WithOpName("Const"), 1, {1, 2, 3, 4}); + Output r = ops::Size(s.WithOpName("Size"), c); + Output i = ops::Identity(s.WithOpName("Identity"), r); + + GrapplerItem item; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + GraphProperties properties(item); + TF_CHECK_OK(properties.InferStatically(false)); + const auto size_props = properties.GetOutputProperties("Size"); + const OpInfo::TensorProperties size_props0 = size_props[0]; + EXPECT_EQ("int32: []", PropToString(size_props0)); + EXPECT_TRUE(size_props0.has_value()); + ExpectTensorValues({24}, size_props0.value()); + const auto identity_props = properties.GetOutputProperties("Identity"); + const OpInfo::TensorProperties identity_props0 = identity_props[0]; + EXPECT_EQ("int32: []", PropToString(identity_props0)); + EXPECT_TRUE(identity_props0.has_value()); + ExpectTensorValues({24}, identity_props0.value()); +} + TEST_F(GraphPropertiesTest, PackWithIdentityInput) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); // Same to PackWithConstInput test case, but a, b, c, and d are Identity ops -- GitLab From 64a4cf11f61e8ed7fa332c35ddbcb2a803726694 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 09:36:20 -0700 Subject: [PATCH 0138/1825] Fix TF 2.0 variable API exporting. Apparently @tf_export(v2=[...]) isn't a thing, the correct incantation is @tf_export(..., v1=[]). Which makes sense since we want the symbol to continue to exist in v3. PiperOrigin-RevId: 217331254 --- tensorflow/python/ops/variable_scope.py | 2 +- tensorflow/python/ops/variables.py | 2 +- ...ensorflow.-variable.-save-slice-info.pbtxt | 17 +++ .../api/golden/v2/tensorflow.-variable.pbtxt | 130 ++++++++++++++++++ .../tools/api/golden/v2/tensorflow.pbtxt | 8 ++ 5 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-variable.-save-slice-info.pbtxt create mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt diff --git a/tensorflow/python/ops/variable_scope.py b/tensorflow/python/ops/variable_scope.py index 5032ca79f9..9b10af9182 100644 --- a/tensorflow/python/ops/variable_scope.py +++ b/tensorflow/python/ops/variable_scope.py @@ -2558,7 +2558,7 @@ def variable_creator_scope_v1(variable_creator): # Note: only the docstrings differ between this and v1. -@tf_export(v2=["variable_creator_scope"]) +@tf_export("variable_creator_scope", v1=[]) @tf_contextlib.contextmanager def variable_creator_scope(variable_creator): """Scope which defines a variable creation function to be used by variable(). diff --git a/tensorflow/python/ops/variables.py b/tensorflow/python/ops/variables.py index 84871f09f9..df7b7f920f 100644 --- a/tensorflow/python/ops/variables.py +++ b/tensorflow/python/ops/variables.py @@ -187,7 +187,7 @@ class VariableMetaclass(type): return super(VariableMetaclass, cls).__call__(*args, **kwargs) -@tf_export(v2=["Variable"]) +@tf_export("Variable", v1=[]) class Variable(six.with_metaclass(VariableMetaclass, checkpointable.CheckpointableBase)): """See the [Variables Guide](https://tensorflow.org/guide/variables). diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-variable.-save-slice-info.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-variable.-save-slice-info.pbtxt new file mode 100644 index 0000000000..ac3ccd468b --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.-variable.-save-slice-info.pbtxt @@ -0,0 +1,17 @@ +path: "tensorflow.Variable.SaveSliceInfo" +tf_class { + is_instance: "" + is_instance: "" + member { + name: "spec" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'full_name\', \'full_shape\', \'var_offset\', \'var_shape\', \'save_slice_info_def\', \'import_scope\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\', \'None\', \'None\', \'None\'], " + } + member_method { + name: "to_proto" + argspec: "args=[\'self\', \'export_scope\'], 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 new file mode 100644 index 0000000000..e85949f23c --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.-variable.pbtxt @@ -0,0 +1,130 @@ +path: "tensorflow.Variable" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "SaveSliceInfo" + mtype: "" + } + member { + name: "constraint" + mtype: "" + } + member { + name: "device" + mtype: "" + } + member { + name: "dtype" + mtype: "" + } + member { + name: "graph" + mtype: "" + } + member { + name: "initial_value" + mtype: "" + } + member { + name: "initializer" + mtype: "" + } + member { + name: "name" + mtype: "" + } + member { + name: "op" + mtype: "" + } + member { + name: "shape" + mtype: "" + } + member { + name: "trainable" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'initial_value\', \'trainable\', \'validate_shape\', \'caching_device\', \'name\', \'variable_def\', \'dtype\', \'import_scope\', \'constraint\', \'synchronization\', \'aggregation\'], varargs=None, keywords=None, defaults=[\'None\', \'True\', \'True\', \'None\', \'None\', \'None\', \'None\', \'None\', \'None\', \'VariableSynchronization.AUTO\', \'VariableAggregation.NONE\'], " + } + member_method { + name: "assign" + argspec: "args=[\'self\', \'value\', \'use_locking\', \'name\', \'read_value\'], varargs=None, keywords=None, defaults=[\'False\', \'None\', \'True\'], " + } + member_method { + name: "assign_add" + argspec: "args=[\'self\', \'delta\', \'use_locking\', \'name\', \'read_value\'], varargs=None, keywords=None, defaults=[\'False\', \'None\', \'True\'], " + } + member_method { + name: "assign_sub" + argspec: "args=[\'self\', \'delta\', \'use_locking\', \'name\', \'read_value\'], varargs=None, keywords=None, defaults=[\'False\', \'None\', \'True\'], " + } + member_method { + name: "count_up_to" + argspec: "args=[\'self\', \'limit\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "eval" + argspec: "args=[\'self\', \'session\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "from_proto" + argspec: "args=[\'variable_def\', \'import_scope\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "get_shape" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "initialized_value" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "load" + argspec: "args=[\'self\', \'value\', \'session\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "read_value" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "scatter_add" + argspec: "args=[\'self\', \'sparse_delta\', \'use_locking\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " + } + member_method { + name: "scatter_nd_add" + argspec: "args=[\'self\', \'indices\', \'updates\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "scatter_nd_sub" + argspec: "args=[\'self\', \'indices\', \'updates\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "scatter_nd_update" + argspec: "args=[\'self\', \'indices\', \'updates\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "scatter_sub" + argspec: "args=[\'self\', \'sparse_delta\', \'use_locking\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " + } + member_method { + name: "scatter_update" + argspec: "args=[\'self\', \'sparse_delta\', \'use_locking\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'None\'], " + } + member_method { + name: "set_shape" + argspec: "args=[\'self\', \'shape\'], varargs=None, keywords=None, defaults=None" + } + member_method { + name: "to_proto" + argspec: "args=[\'self\', \'export_scope\'], varargs=None, keywords=None, defaults=[\'None\'], " + } + member_method { + name: "value" + argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt index 3664eef406..4b4d150aa1 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt @@ -176,6 +176,10 @@ tf_module { name: "VarLenFeature" mtype: "" } + member { + name: "Variable" + mtype: "" + } member { name: "VariableAggregation" mtype: "" @@ -1700,6 +1704,10 @@ tf_module { name: "variable_axis_size_partitioner" argspec: "args=[\'max_shard_bytes\', \'axis\', \'bytes_per_string_element\', \'max_shards\'], varargs=None, keywords=None, defaults=[\'0\', \'16\', \'None\'], " } + member_method { + name: "variable_creator_scope" + argspec: "args=[\'variable_creator\'], varargs=None, keywords=None, defaults=None" + } member_method { name: "where" argspec: "args=[\'condition\', \'x\', \'y\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " -- GitLab From 2a91929a3b8bfd9ca8a823035b3654f2ae031ef2 Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Tue, 16 Oct 2018 09:37:33 -0700 Subject: [PATCH 0139/1825] Add cond_v2 dependency back to control_flow_ops_py_test. This is necessary for control_flow_ops.py to lazy-load cond_v2. PiperOrigin-RevId: 217331510 --- tensorflow/python/kernel_tests/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index fa26690718..33fb925f09 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1490,6 +1490,7 @@ cuda_py_test( "//tensorflow/python:array_ops_gen", "//tensorflow/python:client", "//tensorflow/python:client_testlib", + "//tensorflow/python:cond_v2", "//tensorflow/python:control_flow_ops", "//tensorflow/python:data_flow_ops", "//tensorflow/python:data_flow_ops_gen", -- GitLab From 41fc46f2a8b0d0ecdc80e39ffcb211eefd683865 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 09:46:54 -0700 Subject: [PATCH 0140/1825] Fix OSS build breaks. PiperOrigin-RevId: 217333247 --- .../compiler/xla/service/gpu/cudnn_conv_rewriter_test.cc | 6 ++++-- .../xla/service/gpu/tests/cudnn_fused_conv_rewriter_test.cc | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_conv_rewriter_test.cc b/tensorflow/compiler/xla/service/gpu/cudnn_conv_rewriter_test.cc index e7f572d01b..a6980850af 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_conv_rewriter_test.cc +++ b/tensorflow/compiler/xla/service/gpu/cudnn_conv_rewriter_test.cc @@ -127,8 +127,10 @@ TEST_F(CudnnConvRewriterTest, BackwardFilterConvolve) { op::CustomCall(kCudnnConvBackwardFilterCallTarget), 0)); // Check that metadata was preserved. - EXPECT_THAT(entry_computation->root_instruction()->operand(0)->metadata(), - ::testing::EqualsProto(metadata)); + const auto& md_after_opt = + entry_computation->root_instruction()->operand(0)->metadata(); + EXPECT_TRUE(protobuf_util::ProtobufEquals(md_after_opt, metadata)) + << md_after_opt.DebugString() << " vs " << metadata.DebugString(); } TEST_F(CudnnConvRewriterTest, diff --git a/tensorflow/compiler/xla/service/gpu/tests/cudnn_fused_conv_rewriter_test.cc b/tensorflow/compiler/xla/service/gpu/tests/cudnn_fused_conv_rewriter_test.cc index 12146068ed..b7dd07a50c 100644 --- a/tensorflow/compiler/xla/service/gpu/tests/cudnn_fused_conv_rewriter_test.cc +++ b/tensorflow/compiler/xla/service/gpu/tests/cudnn_fused_conv_rewriter_test.cc @@ -302,7 +302,7 @@ TEST_F(CudnnFusedConvRewriterTest, PreservesMetadata) { ->ToString(); EXPECT_THAT( optimized_hlo_string, - ::testing::ContainsRegex(R"(custom-call.*metadata={op_type="foo"})")); + ::testing::ContainsRegex(R"(custom-call.*metadata=\{op_type="foo"\})")); } } // namespace -- GitLab From 77456c6611fd2faa07d6efabafb3f61f82a24eed Mon Sep 17 00:00:00 2001 From: Guangda Lai <31743510+aaroey@users.noreply.github.com> Date: Tue, 16 Oct 2018 10:06:28 -0700 Subject: [PATCH 0141/1825] Fix broken tests caused by newly added op converters. --- .../contrib/tensorrt/convert/convert_nodes.cc | 4 +- .../tensorrt/convert/convert_nodes_test.cc | 12 +- tensorflow/contrib/tensorrt/test/base_test.py | 10 + .../tensorrt/test/batch_matmul_test.py | 11 +- .../tensorrt/test/biasadd_matmul_test.py | 54 +-- .../binary_tensor_weight_broadcast_test.py | 106 ++---- .../tensorrt/test/reshape_transpose_test.py | 325 +++--------------- 7 files changed, 120 insertions(+), 402 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index 8f697f2af9..85c9e62f10 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -1374,11 +1374,11 @@ tensorflow::Status ConvertReshape( // Product of input shape should equal product of new_dims if (TrtDimsNumElements(input_dims) != TrtDimsNumElements(new_dims)) { return tensorflow::errors::Unimplemented( - "Reshape on the batch dimension is not supported."); + "Reshape on batch dimension is not supported, at ", node_def.name()); } } else if (weights_ptr[0] != ctx.GetMaxBatchSize()) { return tensorflow::errors::Unimplemented( - "Reshape on the batch dimension is not supported."); + "Reshape on batch dimension is not supported, at ", node_def.name()); } const nvinfer1::ITensor* output_tensor = nullptr; diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc index 5c9ddaec49..7c16d5b44e 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes_test.cc @@ -69,10 +69,6 @@ class FakeITensor : public nvinfer1::ITensor { FakeITensor(const string& name, const std::vector& dims) : name_(name), dims_(GetTestDims(dims)) {} - void SetDims(const std::vector& dims) { - setDimensions(GetTestDims(dims)); - } - void setName(const char* name) override { name_ = name; } const char* getName() const override { return name_.c_str(); } @@ -99,6 +95,10 @@ class FakeITensor : public nvinfer1::ITensor { location_ = location; } +#if NV_TENSORRT_MAJOR >= 5 + bool setDynamicRange(float min, float max) override {} +#endif + private: string name_; nvinfer1::Dims dims_; @@ -609,7 +609,7 @@ TEST_F(ConverterTest, ConvertReshape) { AddTestTensor("input", {1, 2, 3}); AddTestWeights("weights", DT_INT32, {4}, {-1, 1, 1, 2}); ExpectStatus(converter_.ConvertNode(node_def), error::UNIMPLEMENTED, - "Reshape on the batch dimension is not supported"); + "Reshape on batch dimension is not supported, at my_reshape"); } { // Reshape at batch dimension, should fail. @@ -617,7 +617,7 @@ TEST_F(ConverterTest, ConvertReshape) { AddTestTensor("input", {1, 2, 3}); AddTestWeights("weights", DT_INT32, {4}, {3, 1, 1, 2}); ExpectStatus(converter_.ConvertNode(node_def), error::UNIMPLEMENTED, - "Reshape on the batch dimension is not supported"); + "Reshape on batch dimension is not supported, at my_reshape"); } // Reshape on non batch dimensions, ok. for (int batch_dim : {-1, 1}) { diff --git a/tensorflow/contrib/tensorrt/test/base_test.py b/tensorflow/contrib/tensorrt/test/base_test.py index 7e9ffb05ab..18096e0ff1 100644 --- a/tensorflow/contrib/tensorrt/test/base_test.py +++ b/tensorflow/contrib/tensorrt/test/base_test.py @@ -136,6 +136,16 @@ class SimpleMultiEnginesTest(trt_test.TfTrtIntegrationTestBase): # - my_trt_op_1 should have ["weights","conv", "div"] return ["my_trt_op_0", "my_trt_op_1"] + def ShouldRunTest(self, run_params): + # TODO(aaroey): LayoutOptimizer adds Transpose(Const, Const) to the graph + # which breaks the conversion. We should fix it as: + # - Detect the invalid NodeDef earlier before adding them to segment + # - Let it able to change the RewriterConfig when calling + # create_inference_graph(). + # It will be good to add debugging feature for Grappler to print the graph + # after running each optimizer. + return False + class PartiallyConvertedTestA(trt_test.TfTrtIntegrationTestBase): diff --git a/tensorflow/contrib/tensorrt/test/batch_matmul_test.py b/tensorflow/contrib/tensorrt/test/batch_matmul_test.py index 2f153c6f2f..4b88808178 100644 --- a/tensorflow/contrib/tensorrt/test/batch_matmul_test.py +++ b/tensorflow/contrib/tensorrt/test/batch_matmul_test.py @@ -50,17 +50,22 @@ class BatchMatMulTest(trt_test.TfTrtIntegrationTestBase): w2 = array_ops.placeholder(dtype=dtype, shape=w2_dims, name=w2_name) with g.device("/GPU:0"): b = constant_op.constant(np.random.randn(12, 5, 12, 7), dtype=dtype) - c = constant_op.constant(np.random.randn(5, 1, 1), dtype=dtype) - d = constant_op.constant(np.random.randn(5, 1, 1), dtype=dtype) x1 = math_ops.matmul(inp, b) + c = constant_op.constant(np.random.randn(5, 1, 1), dtype=dtype) x1 = x1 + c + x2 = math_ops.matmul(inp, w1) + d = constant_op.constant(np.random.randn(5, 1, 1), dtype=dtype) x2 = x2 * d - e = gen_array_ops.reshape(inp, [12, 40, 12]) + + e = self.trt_incompatible_op(inp) + e = gen_array_ops.reshape(e, [12, 40, 12]) x3 = math_ops.matmul(e, w2) f = constant_op.constant(np.random.randn(40, 1), dtype=dtype) x3 = x3 + f x3 = gen_array_ops.reshape(x3, [12, 5, 8, 7]) + x3 = self.trt_incompatible_op(x3) + out = x1 + x2 + x3 array_ops.squeeze(out, name=output_name) return trt_test.TfTrtIntegrationTestParams( diff --git a/tensorflow/contrib/tensorrt/test/biasadd_matmul_test.py b/tensorflow/contrib/tensorrt/test/biasadd_matmul_test.py index 15b301177f..74a2177d88 100644 --- a/tensorflow/contrib/tensorrt/test/biasadd_matmul_test.py +++ b/tensorflow/contrib/tensorrt/test/biasadd_matmul_test.py @@ -37,91 +37,91 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): """Testing conversion of BiasAdd MatMul in TF-TRT conversion.""" dtype = dtypes.float32 input_name = "input" - input_dims = [48, 12] + input_dims = [4, 144] output_name = "output" g = ops.Graph() with g.as_default(): x = array_ops.placeholder(dtype=dtype, shape=input_dims, name=input_name) - b = constant_op.constant(np.random.randn(12, 4), dtype=dtype) + b = constant_op.constant(np.random.randn(144, 4), dtype=dtype) x1 = math_ops.matmul(x, b) b = constant_op.constant(np.random.randn(1, 4), dtype=dtype) x1 = x1 + b - b = constant_op.constant(np.random.randn(48, 4), dtype=dtype) - x2 = math_ops.matmul(x, b, transpose_a=True) - x2 = gen_array_ops.reshape(x2, [48, 1]) + b = constant_op.constant(np.random.randn(4, 144), dtype=dtype) + x2 = self.trt_incompatible_op(x) + x2 = math_ops.matmul(x2, b, transpose_a=True) + x2 = gen_array_ops.reshape(x2, [4, -1]) + x2 = self.trt_incompatible_op(x2) - b = constant_op.constant(np.random.randn(4, 12), dtype=dtype) + b = constant_op.constant(np.random.randn(4, 144), dtype=dtype) x3 = math_ops.matmul(x, b, transpose_b=True) - b = constant_op.constant(np.random.randn(16, 48), dtype=dtype) - x4 = math_ops.matmul(x, b, transpose_b=True, transpose_a=True) - x4 = gen_array_ops.reshape(x4, [48, 4]) + b = constant_op.constant(np.random.randn(16, 4), dtype=dtype) + x4 = self.trt_incompatible_op(x) + x4 = math_ops.matmul(x4, b, transpose_b=True, transpose_a=True) + x4 = gen_array_ops.reshape(x4, [4, -1]) + x4 = self.trt_incompatible_op(x4) - x5 = gen_array_ops.reshape(x, [4, 144]) b = constant_op.constant(np.random.randn(144, 48), dtype=dtype) - x5 = math_ops.matmul(x5, b) + x5 = math_ops.matmul(x, b) b = constant_op.constant(np.random.randn(48), dtype=dtype) x5 = nn.bias_add(x5, b) - x5 = gen_array_ops.reshape(x5, [48, 4]) + x5 = gen_array_ops.reshape(x5, [4, -1]) x6 = gen_array_ops.reshape(x, [4, 12, 12]) b = constant_op.constant(np.random.randn(12), dtype=dtype) x6 = nn.bias_add(x6, b, data_format="NHWC") - x6 = gen_array_ops.reshape(x6, [48, -1]) + x6 = gen_array_ops.reshape(x6, [4, -1]) x7 = gen_array_ops.reshape(x, [4, 12, 3, 4]) b = constant_op.constant(np.random.randn(4), dtype=dtype) x7 = nn.bias_add(x7, b, data_format="NHWC") - x7 = gen_array_ops.reshape(x7, [48, -1]) + x7 = gen_array_ops.reshape(x7, [4, -1]) x8 = gen_array_ops.reshape(x, [4, 12, 3, 2, 2]) b = constant_op.constant(np.random.randn(2), dtype=dtype) x8 = nn.bias_add(x8, b, data_format="NHWC") - x8 = gen_array_ops.reshape(x8, [48, -1]) + x8 = gen_array_ops.reshape(x8, [4, -1]) x9 = gen_array_ops.reshape(x, [4, 12, 3, 2, 2]) b = constant_op.constant(np.random.randn(12), dtype=dtype) x9 = nn.bias_add(x9, b, data_format="NCHW") - x9 = gen_array_ops.reshape(x9, [48, -1]) + x9 = gen_array_ops.reshape(x9, [4, -1]) x10 = gen_array_ops.reshape(x, [4, 12, 3, 4]) b = constant_op.constant(np.random.randn(12), dtype=dtype) x10 = nn.bias_add(x10, b, data_format="NCHW") - x10 = gen_array_ops.reshape(x10, [48, -1]) + x10 = gen_array_ops.reshape(x10, [4, -1]) x11 = gen_array_ops.reshape(x, [4, 12, 12]) b = constant_op.constant(np.random.randn(12), dtype=dtype) x11 = nn.bias_add(x11, b, data_format="NCHW") - x11 = gen_array_ops.reshape(x11, [48, -1]) + x11 = gen_array_ops.reshape(x11, [4, -1]) - out = array_ops.concat( - [x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11], axis=-1) + 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=[(48, 89)]) + expected_output_dims=[(4, 6680)]) def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" return super(BiasaddMatMulTest, self).GetConversionParams(run_params)._replace( - max_batch_size=48, maximum_cached_engines=2) + max_batch_size=4, maximum_cached_engines=2) def _ValidEngines(self): """Engines expected to build and run.""" - return [ - "my_trt_op_0", "my_trt_op_1", "my_trt_op_2", "my_trt_op_3", - "my_trt_op_6", "my_trt_op_7", "my_trt_op_8", "my_trt_op_9" - ] + return ["my_trt_op_0"] def _InvalidEngines(self): """Engines that will cause conversion error at building time.""" - return ["my_trt_op_4", "my_trt_op_5"] + return ["my_trt_op_1", "my_trt_op_2"] def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" 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 f126ed4238..72d95cca78 100644 --- a/tensorflow/contrib/tensorrt/test/binary_tensor_weight_broadcast_test.py +++ b/tensorflow/contrib/tensorrt/test/binary_tensor_weight_broadcast_test.py @@ -32,79 +32,34 @@ from tensorflow.python.platform import test class BinaryTensorWeightBroadcastTest(trt_test.TfTrtIntegrationTestBase): + def _ConstFn(self, shape): + return constant_op.constant(np.random.randn(*shape), dtype=dtypes.float32) + def GetParams(self): """Tests for scale & elementwise layers in TF-TRT.""" - dtype = dtypes.float32 input_name = "input" input_dims = [10, 24, 24, 20] output_name = "output" g = ops.Graph() with g.as_default(): - x = array_ops.placeholder(dtype=dtype, shape=input_dims, name=input_name) - # scale - a = constant_op.constant(np.random.randn(1), dtype=dtype) - f = x + a - x = math_ops.sigmoid(f) - # scale - a = constant_op.constant(np.random.randn(1), dtype=dtype) - f = a + x - x = math_ops.sigmoid(f) - # scale - a = constant_op.constant(np.random.randn(24, 1, 1), dtype=dtype) - f = x + a - x = math_ops.sigmoid(f) - # scale - a = constant_op.constant(np.random.randn(24, 1, 1), dtype=dtype) - f = a + x - x = math_ops.sigmoid(f) - # scale - a = constant_op.constant(np.random.randn(24, 24, 20), dtype=dtype) - f = a + x - x = math_ops.sigmoid(f) - # scale - a = constant_op.constant(np.random.randn(24, 24, 20), dtype=dtype) - f = x + a - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(20), dtype=dtype) - f = x + a - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(20), dtype=dtype) - f = a + x - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(1, 24, 1, 1), dtype=dtype) - f = a + x - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(1, 24, 1, 1), dtype=dtype) - f = x + a - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(1, 24, 24, 1), dtype=dtype) - f = a + x - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(1, 24, 24, 1), dtype=dtype) - f = x + a - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(1, 24, 24, 20), dtype=dtype) - f = a + x - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(1, 24, 24, 20), dtype=dtype) - f = x + a - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(24, 20), dtype=dtype) - f = a + x - x = math_ops.sigmoid(f) - # elementwise - a = constant_op.constant(np.random.randn(24, 20), dtype=dtype) - f = x + a - x = math_ops.sigmoid(f) + x = array_ops.placeholder( + dtype=dtypes.float32, shape=input_dims, name=input_name) + for weights_shape in [ + (1,), # scale + (24, 1, 1), # scale + (24, 24, 20), # scale + (20,), # elementwise + (1, 24, 1, 1), # elementwise + (1, 24, 24, 1), # elementwise + (1, 24, 24, 20), # elementwise + (24, 20), # elementwise + ]: + a = self._ConstFn(weights_shape) + f = x + a + x = math_ops.sigmoid(f) + a = self._ConstFn(weights_shape) + f = a + x + x = math_ops.sigmoid(f) gen_array_ops.reshape(x, [5, -1], name=output_name) return trt_test.TfTrtIntegrationTestParams( gdef=g.as_graph_def(), @@ -115,24 +70,7 @@ class BinaryTensorWeightBroadcastTest(trt_test.TfTrtIntegrationTestBase): def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" - return [ - "my_trt_op_0", - "my_trt_op_1", - "my_trt_op_2", - "my_trt_op_3", - "my_trt_op_4", - "my_trt_op_5", - "my_trt_op_6", - "my_trt_op_7", - "my_trt_op_8", - "my_trt_op_9", - "my_trt_op_10", - "my_trt_op_11", - "my_trt_op_12", - "my_trt_op_13", - "my_trt_op_14", - "my_trt_op_15", - ] + return ["my_trt_op_%d" % i for i in range(16)] if __name__ == "__main__": diff --git a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py index 61d95bb242..e22929f114 100644 --- a/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py +++ b/tensorflow/contrib/tensorrt/test/reshape_transpose_test.py @@ -20,22 +20,17 @@ from __future__ import print_function import numpy as np -from tensorflow.contrib.tensorrt.python import trt_convert 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 math_ops -from tensorflow.python.ops import nn -from tensorflow.python.ops import nn_ops from tensorflow.python.platform import test -class SimpleReshapeTest(trt_test.TfTrtIntegrationTestBase): +class ReshapeTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): - """Create a graph containing single segment.""" dtype = dtypes.float32 input_name = "input" input_dims = [100, 24, 24, 2] @@ -44,96 +39,51 @@ class SimpleReshapeTest(trt_test.TfTrtIntegrationTestBase): with g.as_default(): inp = array_ops.placeholder( dtype=dtype, shape=[None] + input_dims[1:], name=input_name) + outputs = [] + # Here we test two types of reshapes, one changes the batch dimension and + # the other does not. Note that we're not able to test reshaping to + # scalar, since TRT requires input tensor to be of rank at least 2, so a + # reshape with scalar input will be filtered out of the segment before + # conversion. with g.device("/GPU:0"): - reshape = array_ops.reshape(inp, [-1, 24*24*2]) - # Add identities to ensure we have at least min_segment_size=3 nodes - identity = array_ops.identity(reshape, "identity") - identity = array_ops.identity(identity, "identity2") - array_ops.identity(identity, name=output_name) + # These reshapes happen at batch dimension, thus should fail. + for shape in [[2, 50, 24, 24, 2], [-1, 50, 24, 24, 2], + [2, 50, -1, 24, 2]]: + r = array_ops.reshape(inp, shape) + r = array_ops.reshape(r, [-1, 24, 24, 2]) + outputs.append(self.trt_incompatible_op(r)) + # Add another block with many reshapes that don't change the batch + # dimension. + r = array_ops.reshape(inp, [-1, 24 * 24, 2], name="reshape-0") + r = array_ops.reshape(r, [100, 24, -1], name="reshape-1") + r = array_ops.reshape(r, [100, 24 * 2, 24], name="reshape-2") + r = array_ops.reshape(r, [-1, 24, 24 * 2], name="reshape-3") + r = array_ops.reshape(r, [-1, 6, 4, 24, 2], name="reshape-4") + r = array_ops.reshape(r, [-1, 6, 4, 6, 4, 2, 1], name="reshape-5") + r = array_ops.reshape(r, [-1, 24, 24, 2], name="reshape-6") + outputs.append(self.trt_incompatible_op(r)) + math_ops.add_n(outputs, 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=[(100, 24*24*2)]) + expected_output_dims=[tuple(input_dims)]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" - return ["my_trt_op_0"] - -class ReshapeToScalarTest(trt_test.TfTrtIntegrationTestBase): - - def GetParams(self): - """Create a graph containing single segment.""" - dtype = dtypes.float32 - input_name = "input" - input_dims = [1] - output_name = "output" - g = ops.Graph() - with g.as_default(): - inp = array_ops.placeholder( - dtype=dtype, shape=input_dims, name=input_name) - with g.device("/GPU:0"): - reshape = array_ops.reshape(inp, []) - # Add identities to ensure we have at least min_segment_size=3 nodes - identity = array_ops.identity(reshape, "identity") - identity = array_ops.identity(identity, "identity2") - array_ops.identity(identity, 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=[()]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return [] + return { + "my_trt_op_3": ["reshape-%d" % i for i in range(7)] + + ["reshape-%d/shape" % i for i in range(7)] + } def ShouldRunTest(self, run_params): """Whether to run the test.""" - # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not - # a calib graph. Doesn't seem to contain any calibration nodes."" - return (not trt_test.IsQuantizationMode(run_params.precision_mode) and + return (not trt_test.IsQuantizationMode(run_params.precision_mode) and not run_params.dynamic_engine) -class ReshapeBatchDimensionTest(trt_test.TfTrtIntegrationTestBase): - def GetParams(self): - """Create a graph containing single segment.""" - dtype = dtypes.float32 - input_name = "input" - input_dims = [100, 24, 24, 2] - 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"): - reshape = array_ops.reshape(inp, [2, 50, 24, 24, 2]) - # Add identities to ensure we have at least min_segment_size=3 nodes - identity = array_ops.identity(reshape, "identity") - identity = array_ops.identity(identity, "identity2") - array_ops.identity(identity, 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=[(2, 50, 24, 24, 2)]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return [] - - def ShouldRunTest(self, run_params): - """Whether to run the test.""" - # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not - # a calib graph. Doesn't seem to contain any calibration nodes."" - return (not trt_test.IsQuantizationMode(run_params.precision_mode) and - not run_params.dynamic_engine) - -class ReshapeBatchDimensionTest2(trt_test.TfTrtIntegrationTestBase): +class TransposeTest(trt_test.TfTrtIntegrationTestBase): def GetParams(self): """Create a graph containing single segment.""" @@ -146,218 +96,33 @@ class ReshapeBatchDimensionTest2(trt_test.TfTrtIntegrationTestBase): inp = array_ops.placeholder( dtype=dtype, shape=[None] + input_dims[1:], name=input_name) with g.device("/GPU:0"): - reshape = array_ops.reshape(inp, [-1, 50, 24, 24, 2]) - # Add identities to ensure we have at least min_segment_size=3 nodes - identity = array_ops.identity(reshape, "identity") - identity = array_ops.identity(identity, "identity2") - array_ops.identity(identity, name=output_name) + t = array_ops.transpose(inp, [0, 3, 1, 2], name="transpose-1") + t = array_ops.transpose(t, [0, 2, 3, 1], name="transposeback-1") + incompatible = self.trt_incompatible_op(t) + t = array_ops.transpose(incompatible, [2, 1, 0, 3], name="transpose-2") + t = array_ops.transpose(t, [0, 2, 3, 1], name="transpose-3") + array_ops.identity(t, 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=[(2, 50, 24, 24, 2)]) + expected_output_dims=[(24, 100, 2, 24)]) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" - return [] + return { + "my_trt_op_0": [ + "transpose-1", "transpose-1/perm", "transposeback-1", + "transposeback-1/perm" + ] + } def ShouldRunTest(self, run_params): """Whether to run the test.""" - # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not - # a calib graph. Doesn't seem to contain any calibration nodes."" - return (not trt_test.IsQuantizationMode(run_params.precision_mode) and + return (not trt_test.IsQuantizationMode(run_params.precision_mode) and not run_params.dynamic_engine) -class ReshapeBatchDimensionTest3(trt_test.TfTrtIntegrationTestBase): - - def GetParams(self): - """Create a graph containing single segment.""" - dtype = dtypes.float32 - input_name = "input" - input_dims = [100, 24, 24, 2] - 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"): - reshape = array_ops.reshape(inp, [2, 50, -1, 24, 2]) - # Add identities to ensure we have at least min_segment_size=3 nodes - identity = array_ops.identity(reshape, "identity") - identity = array_ops.identity(identity, "identity2") - array_ops.identity(identity, 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=[(2, 50, 24, 24, 2)]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return [] - - def ShouldRunTest(self, run_params): - """Whether to run the test.""" - # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not - # a calib graph. Doesn't seem to contain any calibration nodes."" - return (not trt_test.IsQuantizationMode(run_params.precision_mode) and - not run_params.dynamic_engine) - -class ReshapeInverseTest(trt_test.TfTrtIntegrationTestBase): - - def GetParams(self): - """Create a graph containing single segment.""" - dtype = dtypes.float32 - input_name = "input" - input_dims = [100, 24, 24, 2] - 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"): - reshape = array_ops.reshape(inp, [-1, 24*24*2]) - reshape = array_ops.reshape(reshape, [-1, 24, 24, 2]) - identity = array_ops.identity(reshape, "identity") - array_ops.identity(identity, 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=[(100, 24, 24, 2)]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return ["my_trt_op_0"] - -class ManyReshapeTest(trt_test.TfTrtIntegrationTestBase): - - def GetParams(self): - """Create a graph containing single segment.""" - dtype = dtypes.float32 - input_name = "input" - input_dims = [100, 24, 24, 2] - 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"): - reshape = array_ops.reshape(inp, [-1, 24*24, 2]) - reshape = array_ops.reshape(reshape, [-1, 24*2, 24]) - reshape = array_ops.reshape(reshape, [-1, 24, 24*2]) - reshape = array_ops.reshape(reshape, [-1, 6, 4, 24, 2]) - reshape = array_ops.reshape(reshape, [-1, 6, 4, 6, 4, 2]) - reshape = array_ops.reshape(reshape, [-1, 6, 4, 6, 4, 2, 1]) - reshape = array_ops.reshape(reshape, [-1, 24, 24, 2]) - identity = array_ops.identity(reshape, "identity") - array_ops.identity(identity, 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=[(100, 24, 24, 2)]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return ["my_trt_op_0"] - -class SimpleTransposeTest(trt_test.TfTrtIntegrationTestBase): - - def GetParams(self): - """Create a graph containing single segment.""" - dtype = dtypes.float32 - input_name = "input" - input_dims = [100, 24, 24, 2] - 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"): - # to NCHW - transpose = array_ops.transpose(inp, [0, 3, 1, 2]) - identity = array_ops.identity(transpose, "identity") - array_ops.identity(identity, 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=[(100, 2, 24, 24)]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return ["my_trt_op_0"] - -class TransposeBatchDimensionTest(trt_test.TfTrtIntegrationTestBase): - - def GetParams(self): - """Create a graph containing single segment.""" - dtype = dtypes.float32 - input_name = "input" - input_dims = [100, 24, 24, 2] - 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"): - # to NCHW - transpose = array_ops.transpose(inp, [2, 1, 0, 3]) - identity = array_ops.identity(transpose, "identity") - array_ops.identity(identity, 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=[(24, 24, 100, 2)]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return [] - - def ShouldRunTest(self, run_params): - """Whether to run the test.""" - # No engine should be created so exclude INT8 to avoid "ERROR:tensorflow:Not - # a calib graph. Doesn't seem to contain any calibration nodes."" - return (not trt_test.IsQuantizationMode(run_params.precision_mode) and - not run_params.dynamic_engine) - -class TransposeInverseTest(trt_test.TfTrtIntegrationTestBase): - - def GetParams(self): - """Create a graph containing single segment.""" - dtype = dtypes.float32 - input_name = "input" - input_dims = [100, 24, 24, 2] - 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"): - # to NCHW - transpose = array_ops.transpose(inp, [0, 3, 1, 2]) - # back to NHWC - transpose = array_ops.transpose(transpose, [0, 2, 3, 1]) - identity = array_ops.identity(transpose, "identity") - array_ops.identity(identity, 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=[(100, 24, 24, 2)]) - - def ExpectedEnginesToBuild(self, run_params): - """Return the expected engines to build.""" - return ["my_trt_op_0"] if __name__ == "__main__": test.main() -- GitLab From d06d64c34e730253abb596cc081793132a5b985d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 10:27:54 -0700 Subject: [PATCH 0142/1825] Add Eager execution to all tests in backend.py PiperOrigin-RevId: 217341177 --- tensorflow/python/keras/backend.py | 19 +- tensorflow/python/keras/backend_test.py | 452 +++++++++++++----------- 2 files changed, 260 insertions(+), 211 deletions(-) diff --git a/tensorflow/python/keras/backend.py b/tensorflow/python/keras/backend.py index 708a442e71..032077cd12 100644 --- a/tensorflow/python/keras/backend.py +++ b/tensorflow/python/keras/backend.py @@ -826,6 +826,9 @@ def placeholder(shape=None, ndim=None, dtype=None, sparse=False, name=None): sparse: Boolean, whether the placeholder should have a sparse type. name: Optional name string for the placeholder. + Raises: + ValueError: If called with eager execution. + Returns: Tensor instance (with Keras metadata included). @@ -837,6 +840,9 @@ def placeholder(shape=None, ndim=None, dtype=None, sparse=False, name=None): ``` """ + if context.executing_eagerly(): + raise ValueError( + '`keras.backend.placeholder` is not supported with eager execution.') if dtype is None: dtype = floatx() if not shape: @@ -1007,7 +1013,7 @@ def eval(x): [ 3., 4.]], dtype=float32) ``` """ - return to_dense(x).eval(session=get_session()) + return get_value(to_dense(x)) @tf_export('keras.backend.zeros') @@ -3060,8 +3066,11 @@ def function(inputs, outputs, updates=None, **kwargs): Output values as Numpy arrays. Raises: - ValueError: if invalid kwargs are passed in. + ValueError: if invalid kwargs are passed in or if in eager execution. """ + if context.executing_eagerly(): + raise ValueError( + '`keras.backend.function` is not supported with eager execution.') if kwargs: for key in kwargs: if (key not in tf_inspect.getfullargspec(session_module.Session.run)[0] @@ -4256,6 +4265,8 @@ def separable_conv2d(x, data_format = image_data_format() if data_format not in {'channels_first', 'channels_last'}: raise ValueError('Unknown data_format: ' + str(data_format)) + if len(strides) != 2: + raise ValueError('`strides` must be a tuple of 2 integers.') x, tf_data_format = _preprocess_conv2d_input(x, data_format) padding = _preprocess_padding(padding) @@ -4462,6 +4473,10 @@ def pool2d(x, data_format = image_data_format() if data_format not in {'channels_first', 'channels_last'}: raise ValueError('Unknown data_format: ' + str(data_format)) + if len(pool_size) != 2: + raise ValueError('`pool_size` must be a tuple of 2 integers.') + if len(strides) != 2: + raise ValueError('`strides` must be a tuple of 2 integers.') x, tf_data_format = _preprocess_conv2d_input(x, data_format) padding = _preprocess_padding(padding) diff --git a/tensorflow/python/keras/backend_test.py b/tensorflow/python/keras/backend_test.py index 0834448699..4368b69ebe 100644 --- a/tensorflow/python/keras/backend_test.py +++ b/tensorflow/python/keras/backend_test.py @@ -23,9 +23,12 @@ import scipy.sparse from tensorflow.core.protobuf import config_pb2 from tensorflow.python import keras +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 from tensorflow.python.framework import sparse_tensor +from tensorflow.python.framework import test_util from tensorflow.python.ops import nn from tensorflow.python.ops import variables from tensorflow.python.platform import test @@ -89,6 +92,7 @@ def compare_two_inputs_op_to_numpy(keras_op, str(keras_output)) +@test_util.run_all_in_graph_and_eager_modes class BackendUtilsTest(test.TestCase): def test_backend(self): @@ -130,8 +134,9 @@ class BackendUtilsTest(test.TestCase): keras.backend.set_learning_phase(0) x = keras.Input((3,)) y = keras.layers.BatchNormalization()(x) - sess.run(variables.global_variables_initializer()) - sess.run(y, feed_dict={x: np.random.random((2, 3))}) + if not context.executing_eagerly(): + sess.run(variables.global_variables_initializer()) + sess.run(y, feed_dict={x: np.random.random((2, 3))}) def test_learning_phase_scope(self): with self.cached_session(): @@ -150,22 +155,29 @@ class BackendUtilsTest(test.TestCase): self.assertEqual(keras.backend.learning_phase(), initial_learning_phase) def test_int_shape(self): - x = keras.backend.placeholder(shape=(3, 4)) + x = keras.backend.ones(shape=(3, 4)) self.assertEqual(keras.backend.int_shape(x), (3, 4)) - x = keras.backend.placeholder(shape=(None, 4)) - self.assertEqual(keras.backend.int_shape(x), (None, 4)) + if not context.executing_eagerly(): + x = keras.backend.placeholder(shape=(None, 4)) + self.assertEqual(keras.backend.int_shape(x), (None, 4)) def test_in_train_phase(self): with self.cached_session(): y1 = keras.backend.variable(1) y2 = keras.backend.variable(2) - y = keras.backend.in_train_phase(y1, y2) - f = keras.backend.function([keras.backend.learning_phase()], [y]) - y_val = f([0])[0] - self.assertAllClose(y_val, 2) - y_val = f([1])[0] - self.assertAllClose(y_val, 1) + if context.executing_eagerly(): + with keras.backend.learning_phase_scope(0): + y_val_test = keras.backend.in_train_phase(y1, y2).numpy() + with keras.backend.learning_phase_scope(1): + y_val_train = keras.backend.in_train_phase(y1, y2).numpy() + else: + y = keras.backend.in_train_phase(y1, y2) + f = keras.backend.function([keras.backend.learning_phase()], [y]) + y_val_test = f([0])[0] + y_val_train = f([1])[0] + self.assertAllClose(y_val_test, 2) + self.assertAllClose(y_val_train, 1) def test_is_keras_tensor(self): x = keras.backend.variable(1) @@ -175,164 +187,20 @@ class BackendUtilsTest(test.TestCase): with self.assertRaises(ValueError): keras.backend.is_keras_tensor(0) - def test_is_placeholder(self): - x = keras.backend.placeholder(shape=(1,)) - self.assertEqual(keras.backend.is_placeholder(x), True) - # Test with TF placeholder - x = keras.backend.array_ops.placeholder(dtype='float32', shape=(1,)) - self.assertEqual(keras.backend.is_placeholder(x), True) - x = keras.backend.variable(1) - self.assertEqual(keras.backend.is_placeholder(x), False) - def test_stop_gradient(self): x = keras.backend.variable(1) y = keras.backend.stop_gradient(x) - self.assertEqual(y.op.name[:12], 'StopGradient') + if not context.executing_eagerly(): + self.assertEqual(y.op.name[:12], 'StopGradient') xs = [keras.backend.variable(1) for _ in range(3)] ys = keras.backend.stop_gradient(xs) - for y in ys: - self.assertEqual(y.op.name[:12], 'StopGradient') - - def test_function_tf_feed_symbols(self): - with self.cached_session(): - # Test feeding a resource variable to `function`. - x1 = keras.backend.placeholder(shape=()) - x2 = keras.backend.placeholder(shape=()) - lr = keras.backend.learning_phase() # Include a placeholder_with_default. - - y1 = keras.backend.variable(10.) - y2 = 3 - - f = keras.backend.function( - inputs=[x1, x2, lr], - outputs=[x1 + 1, - keras.backend.in_train_phase(x2 + 2, x2 - 1)]) - outs = f([y1, y2, None]) # Use default learning_phase value. - self.assertEqual(outs, [11., 2.]) - outs = f([y1, y2, 1]) # Set learning phase value. - self.assertEqual(outs, [11., 5.]) - - # Test triggering a callable refresh by changing the input. - y3 = keras.backend.constant(20.) # Test with tensor - outs = f([y3, y2, None]) - self.assertEqual(outs, [21., 2.]) - - y4 = 4 # Test with non-symbol - outs = f([y4, y2, None]) - self.assertEqual(outs, [5., 2.]) - - # Test with a different dtype - y5 = keras.backend.constant(10., dtype='float64') - outs = f([y5, y2, None]) - self.assertEqual(outs, [11., 2.]) - - def test_function_tf_fetches(self): - # Additional operations can be passed to tf.Session().run() via its - # `fetches` arguments. In contrast to `updates` argument of - # keras.backend.function() these do not have control dependency on `outputs` - # so they can run in parallel. Also they should not contribute to output of - # keras.backend.function(). - with self.cached_session(): - x = keras.backend.variable(0.) - y = keras.backend.variable(0.) - x_placeholder = keras.backend.placeholder(shape=()) - y_placeholder = keras.backend.placeholder(shape=()) - - f = keras.backend.function(inputs=[x_placeholder, y_placeholder], - outputs=[x_placeholder + y_placeholder], - updates=[(x, x_placeholder + 1.)], - fetches=[keras.backend.update(y, 5.)]) - output = f([10., 20.]) - self.assertEqual(output, [30.]) - self.assertEqual( - keras.backend.get_session().run(fetches=[x, y]), [11., 5.]) - - def test_function_tf_feed_dict(self): - # Additional substitutions can be passed to `tf.Session().run()` via its - # `feed_dict` arguments. Note that the feed_dict is passed once in the - # constructor but we can modify the values in the dictionary. Through - # this feed_dict we can provide additional substitutions besides Keras - # inputs. - with self.cached_session(): - x = keras.backend.variable(0.) - y = keras.backend.variable(0.) - x_placeholder = keras.backend.placeholder(shape=()) - y_placeholder = keras.backend.placeholder(shape=()) - - feed_dict = {y_placeholder: 3.} - fetches = [keras.backend.update(y, y_placeholder * 10.)] - f = keras.backend.function(inputs=[x_placeholder], - outputs=[x_placeholder + 1.], - updates=[(x, x_placeholder + 10.)], - feed_dict=feed_dict, - fetches=fetches) - output = f([10.]) - self.assertEqual(output, [11.]) - self.assertEqual( - keras.backend.get_session().run(fetches=[x, y]), [20., 30.]) - - # updated value in feed_dict will be modified within the K.function() - feed_dict[y_placeholder] = 4. - output = f([20.]) - self.assertEqual(output, [21.]) - self.assertEqual( - keras.backend.get_session().run(fetches=[x, y]), [30., 40.]) - - def test_function_tf_run_options_with_run_metadata(self): - with self.cached_session(): - x_placeholder = keras.backend.placeholder(shape=()) - y_placeholder = keras.backend.placeholder(shape=()) - - run_options = config_pb2.RunOptions(output_partition_graphs=True) - run_metadata = config_pb2.RunMetadata() - # enable run_options. - f = keras.backend.function(inputs=[x_placeholder, y_placeholder], - outputs=[x_placeholder + y_placeholder], - options=run_options, - run_metadata=run_metadata) - output = f([10., 20.]) - self.assertEqual(output, [30.]) - self.assertGreater(len(run_metadata.partition_graphs), 0) - # disable run_options. - f1 = keras.backend.function(inputs=[x_placeholder, y_placeholder], - outputs=[x_placeholder + y_placeholder], - run_metadata=run_metadata) - output1 = f1([10., 20.]) - self.assertEqual(output1, [30.]) - self.assertEqual(len(run_metadata.partition_graphs), 0) - - def test_function_fetch_callbacks(self): - - class CallbackStub(object): - - def __init__(self): - self.times_called = 0 - self.callback_result = 0 - - def _fetch_callback(self, result): - self.times_called += 1 - self.callback_result = result - - with self.cached_session(): - callback = CallbackStub() - x_placeholder = keras.backend.placeholder(shape=()) - y_placeholder = keras.backend.placeholder(shape=()) - - callback_op = x_placeholder * y_placeholder - - f = keras.backend.function( - inputs=[x_placeholder, y_placeholder], - outputs=[x_placeholder + y_placeholder]) - f.fetches.append(callback_op) - f.fetch_callbacks[callback_op] = callback._fetch_callback - - _ = f([10., 20.]) - - self.assertEqual(callback.times_called, 1) - self.assertEqual(callback.callback_result, 200) + if not context.executing_eagerly(): + for y in ys: + self.assertEqual(y.op.name[:12], 'StopGradient') +@test_util.run_all_in_graph_and_eager_modes class BackendVariableTest(test.TestCase): def test_zeros(self): @@ -405,23 +273,18 @@ class BackendVariableTest(test.TestCase): y = keras.backend.to_dense(x) self.assertFalse(keras.backend.is_sparse(y)) - def test_placeholder(self): - x = keras.backend.placeholder(shape=(3, 4)) - self.assertEqual(x.get_shape().as_list(), [3, 4]) - x = keras.backend.placeholder(shape=(3, 4), sparse=True) - self.assertEqual(x.get_shape().as_list(), [3, 4]) - +@test_util.run_all_in_graph_and_eager_modes class BackendLinearAlgebraTest(test.TestCase): def test_dot(self): - x = keras.backend.placeholder(shape=(2, 3)) - y = keras.backend.placeholder(shape=(3, 4)) + x = keras.backend.ones(shape=(2, 3)) + y = keras.backend.ones(shape=(3, 4)) xy = keras.backend.dot(x, y) self.assertEqual(xy.get_shape().as_list(), [2, 4]) - x = keras.backend.placeholder(shape=(32, 28, 3)) - y = keras.backend.placeholder(shape=(3, 4)) + x = keras.backend.ones(shape=(32, 28, 3)) + y = keras.backend.ones(shape=(3, 4)) xy = keras.backend.dot(x, y) self.assertEqual(xy.get_shape().as_list(), [32, 28, 4]) @@ -525,7 +388,8 @@ class BackendLinearAlgebraTest(test.TestCase): # alpha (leaky relu used) relu_op = keras.backend.relu(x, alpha=0.5) - self.assertTrue('LeakyRelu' in relu_op.name) + if not context.executing_eagerly(): + self.assertTrue('LeakyRelu' in relu_op.name) self.assertAllClose(keras.backend.eval(relu_op), [[-2, 0], [2, 7]]) # max_value < some elements @@ -534,7 +398,8 @@ class BackendLinearAlgebraTest(test.TestCase): # nn.relu6 used relu_op = keras.backend.relu(x, max_value=6) - self.assertTrue('Relu6' in relu_op.name) # uses tf.nn.relu6 + if not context.executing_eagerly(): + self.assertTrue('Relu6' in relu_op.name) # uses tf.nn.relu6 self.assertAllClose(keras.backend.eval(relu_op), [[0, 0], [2, 6]]) # max value > 6 @@ -578,6 +443,7 @@ class BackendLinearAlgebraTest(test.TestCase): self.assertAllClose(keras.backend.eval(relu_op), [[-2, -1], [-0.5, 5]]) +@test_util.run_all_in_graph_and_eager_modes class BackendShapeOpsTest(test.TestCase): def test_reshape(self): @@ -663,9 +529,10 @@ class BackendShapeOpsTest(test.TestCase): self.assertEqual(y.get_shape().as_list(), [1, 9, 2]) # Use with a dynamic axis: - x = keras.backend.placeholder(shape=(2, None, 2)) - y = keras.backend.repeat_elements(x, 3, axis=1) - self.assertEqual(y.get_shape().as_list(), [2, None, 2]) + if not context.executing_eagerly(): + x = keras.backend.placeholder(shape=(2, None, 2)) + y = keras.backend.repeat_elements(x, 3, axis=1) + self.assertEqual(y.get_shape().as_list(), [2, None, 2]) def test_repeat(self): x = keras.backend.variable(np.ones((1, 3))) @@ -780,6 +647,7 @@ class BackendShapeOpsTest(test.TestCase): np_kwargs={'data_format': 'channels_first'}) +@test_util.run_all_in_graph_and_eager_modes class BackendNNOpsTest(test.TestCase, parameterized.TestCase): def test_bias_add(self): @@ -799,7 +667,7 @@ class BackendNNOpsTest(test.TestCase, parameterized.TestCase): input_shape_a=(4, 3, 5, 2, 7), input_shape_b=(7,)) - with self.assertRaises(ValueError): + with self.assertRaises((ValueError, errors_impl.InvalidArgumentError)): x = keras.backend.variable((3, 4)) b = keras.backend.variable((3, 4)) keras.backend.bias_add(x, b) @@ -1278,8 +1146,11 @@ class BackendNNOpsTest(test.TestCase, parameterized.TestCase): rnn_fn = rnn_step_fn() inputs = keras.backend.variable(input_val) - initial_states = [keras.backend.variable(init_state_val), - np.concatenate([init_state_val, init_state_val], axis=-1)] + initial_states = [ + keras.backend.variable(init_state_val), + ops.convert_to_tensor( + np.concatenate([init_state_val, init_state_val], axis=-1)) + ] mask = keras.backend.variable(np_mask) kwargs_list = [ @@ -1382,37 +1253,8 @@ class BackendNNOpsTest(test.TestCase, parameterized.TestCase): self.assertEqual(mean.get_shape().as_list(), [3,]) self.assertEqual(var.get_shape().as_list(), [3,]) - def test_batch_normalization(self): - g_val = np.random.random((3,)) - b_val = np.random.random((3,)) - gamma = keras.backend.variable(g_val) - beta = keras.backend.variable(b_val) - - # 3D NHC case - val = np.random.random((10, 5, 3)) - x = keras.backend.variable(val) - mean, var = nn.moments(x, (0, 1), None, None, False) - normed = keras.backend.batch_normalization( - x, mean, var, beta, gamma, axis=-1, epsilon=1e-3) - self.assertEqual(normed.shape.as_list(), [10, 5, 3]) - - # 4D NHWC case - val = np.random.random((10, 5, 5, 3)) - x = keras.backend.variable(val) - mean, var = nn.moments(x, (0, 1, 2), None, None, False) - normed = keras.backend.batch_normalization( - x, mean, var, beta, gamma, axis=-1, epsilon=1e-3) - self.assertEqual(normed.shape.as_list(), [10, 5, 5, 3]) - - # 4D NCHW case - val = np.random.random((10, 3, 5, 5)) - x = keras.backend.variable(val) - mean, var = nn.moments(x, (0, 2, 3), None, None, False) - normed = keras.backend.batch_normalization( - x, mean, var, beta, gamma, axis=1, epsilon=1e-3) - self.assertEqual(normed.shape.as_list(), [10, 3, 5, 5]) - +@test_util.run_all_in_graph_and_eager_modes class TestCTC(test.TestCase): def test_ctc_decode(self): @@ -1518,6 +1360,7 @@ class TestCTC(test.TestCase): self.assertAllClose(res[:, 0], ref, atol=1e-05) +@test_util.run_all_in_graph_and_eager_modes class TestRandomOps(test.TestCase): def test_random_binomial(self): @@ -1545,5 +1388,196 @@ class TestRandomOps(test.TestCase): preds = seq.predict([['tensorflow eager']]) self.assertEqual(preds.shape, (1,)) + +class BackendGraphTests(test.TestCase): + + def test_is_placeholder(self): + x = keras.backend.placeholder(shape=(1,)) + self.assertEqual(keras.backend.is_placeholder(x), True) + # Test with TF placeholder + x = keras.backend.array_ops.placeholder(dtype='float32', shape=(1,)) + self.assertEqual(keras.backend.is_placeholder(x), True) + x = keras.backend.variable(1) + self.assertEqual(keras.backend.is_placeholder(x), False) + + def test_function_tf_feed_symbols(self): + with self.cached_session(): + # Test feeding a resource variable to `function`. + x1 = keras.backend.placeholder(shape=()) + x2 = keras.backend.placeholder(shape=()) + lr = keras.backend.learning_phase() # Include a placeholder_with_default. + + y1 = keras.backend.variable(10.) + y2 = 3 + + f = keras.backend.function( + inputs=[x1, x2, lr], + outputs=[x1 + 1, keras.backend.in_train_phase(x2 + 2, x2 - 1)]) + outs = f([y1, y2, None]) # Use default learning_phase value. + self.assertEqual(outs, [11., 2.]) + outs = f([y1, y2, 1]) # Set learning phase value. + self.assertEqual(outs, [11., 5.]) + + # Test triggering a callable refresh by changing the input. + y3 = keras.backend.constant(20.) # Test with tensor + outs = f([y3, y2, None]) + self.assertEqual(outs, [21., 2.]) + + y4 = 4 # Test with non-symbol + outs = f([y4, y2, None]) + self.assertEqual(outs, [5., 2.]) + + # Test with a different dtype + y5 = keras.backend.constant(10., dtype='float64') + outs = f([y5, y2, None]) + self.assertEqual(outs, [11., 2.]) + + def test_function_tf_fetches(self): + # Additional operations can be passed to tf.Session().run() via its + # `fetches` arguments. In contrast to `updates` argument of + # keras.backend.function() these do not have control dependency on `outputs` + # so they can run in parallel. Also they should not contribute to output of + # keras.backend.function(). + with self.cached_session(): + x = keras.backend.variable(0.) + y = keras.backend.variable(0.) + x_placeholder = keras.backend.placeholder(shape=()) + y_placeholder = keras.backend.placeholder(shape=()) + + f = keras.backend.function( + inputs=[x_placeholder, y_placeholder], + outputs=[x_placeholder + y_placeholder], + updates=[(x, x_placeholder + 1.)], + fetches=[keras.backend.update(y, 5.)]) + output = f([10., 20.]) + self.assertEqual(output, [30.]) + self.assertEqual(keras.backend.get_session().run(fetches=[x, y]), + [11., 5.]) + + def test_function_tf_feed_dict(self): + # Additional substitutions can be passed to `tf.Session().run()` via its + # `feed_dict` arguments. Note that the feed_dict is passed once in the + # constructor but we can modify the values in the dictionary. Through + # this feed_dict we can provide additional substitutions besides Keras + # inputs. + with self.cached_session(): + x = keras.backend.variable(0.) + y = keras.backend.variable(0.) + x_placeholder = keras.backend.placeholder(shape=()) + y_placeholder = keras.backend.placeholder(shape=()) + + feed_dict = {y_placeholder: 3.} + fetches = [keras.backend.update(y, y_placeholder * 10.)] + f = keras.backend.function( + inputs=[x_placeholder], + outputs=[x_placeholder + 1.], + updates=[(x, x_placeholder + 10.)], + feed_dict=feed_dict, + fetches=fetches) + output = f([10.]) + self.assertEqual(output, [11.]) + self.assertEqual(keras.backend.get_session().run(fetches=[x, y]), + [20., 30.]) + + # updated value in feed_dict will be modified within the K.function() + feed_dict[y_placeholder] = 4. + output = f([20.]) + self.assertEqual(output, [21.]) + self.assertEqual(keras.backend.get_session().run(fetches=[x, y]), + [30., 40.]) + + def test_function_tf_run_options_with_run_metadata(self): + with self.cached_session(): + x_placeholder = keras.backend.placeholder(shape=()) + y_placeholder = keras.backend.placeholder(shape=()) + + run_options = config_pb2.RunOptions(output_partition_graphs=True) + run_metadata = config_pb2.RunMetadata() + # enable run_options. + f = keras.backend.function( + inputs=[x_placeholder, y_placeholder], + outputs=[x_placeholder + y_placeholder], + options=run_options, + run_metadata=run_metadata) + output = f([10., 20.]) + self.assertEqual(output, [30.]) + self.assertGreater(len(run_metadata.partition_graphs), 0) + # disable run_options. + f1 = keras.backend.function( + inputs=[x_placeholder, y_placeholder], + outputs=[x_placeholder + y_placeholder], + run_metadata=run_metadata) + output1 = f1([10., 20.]) + self.assertEqual(output1, [30.]) + self.assertEqual(len(run_metadata.partition_graphs), 0) + + def test_function_fetch_callbacks(self): + + class CallbackStub(object): + + def __init__(self): + self.times_called = 0 + self.callback_result = 0 + + def _fetch_callback(self, result): + self.times_called += 1 + self.callback_result = result + + with self.cached_session(): + callback = CallbackStub() + x_placeholder = keras.backend.placeholder(shape=()) + y_placeholder = keras.backend.placeholder(shape=()) + + callback_op = x_placeholder * y_placeholder + + f = keras.backend.function( + inputs=[x_placeholder, y_placeholder], + outputs=[x_placeholder + y_placeholder]) + f.fetches.append(callback_op) + f.fetch_callbacks[callback_op] = callback._fetch_callback + + _ = f([10., 20.]) + + self.assertEqual(callback.times_called, 1) + self.assertEqual(callback.callback_result, 200) + + def test_placeholder(self): + x = keras.backend.placeholder(shape=(3, 4)) + self.assertEqual(x.get_shape().as_list(), [3, 4]) + x = keras.backend.placeholder(shape=(3, 4), sparse=True) + self.assertEqual(x.get_shape().as_list(), [3, 4]) + + def test_batch_normalization(self): + # No eager CPU kernel. + g_val = np.random.random((3,)) + b_val = np.random.random((3,)) + gamma = keras.backend.variable(g_val) + beta = keras.backend.variable(b_val) + + # 3D NHC case + val = np.random.random((10, 5, 3)) + x = keras.backend.variable(val) + mean, var = nn.moments(x, (0, 1), None, None, False) + normed = keras.backend.batch_normalization( + x, mean, var, beta, gamma, axis=-1, epsilon=1e-3) + self.assertEqual(normed.shape.as_list(), [10, 5, 3]) + + # 4D NHWC case + val = np.random.random((10, 5, 5, 3)) + x = keras.backend.variable(val) + mean, var = nn.moments(x, (0, 1, 2), None, None, False) + normed = keras.backend.batch_normalization( + x, mean, var, beta, gamma, axis=-1, epsilon=1e-3) + self.assertEqual(normed.shape.as_list(), [10, 5, 5, 3]) + + # 4D NCHW case + val = np.random.random((10, 3, 5, 5)) + x = keras.backend.variable(val) + mean, var = nn.moments(x, (0, 2, 3), None, None, False) + normed = keras.backend.batch_normalization( + x, mean, var, beta, gamma, axis=1, epsilon=1e-3) + self.assertEqual(normed.shape.as_list(), [10, 3, 5, 5]) + + if __name__ == '__main__': test.main() -- GitLab From 3535836d3a58185335387bb0913da074456919a9 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Tue, 16 Oct 2018 10:54:31 -0700 Subject: [PATCH 0143/1825] To minimize the size of the windows GPU package, set no_tensorflow_py_deps. PiperOrigin-RevId: 217346661 --- tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh b/tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh index 34847e637a..6178d7794d 100644 --- a/tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh +++ b/tensorflow/tools/ci_build/windows/gpu/pip/build_tf_windows.sh @@ -103,7 +103,8 @@ fi run_configure_for_gpu_build -bazel build --announce_rc --config=opt tensorflow/tools/pip_package:build_pip_package || exit $? +bazel build --announce_rc --config=opt --define=no_tensorflow_py_deps=true \ + tensorflow/tools/pip_package:build_pip_package || exit $? if [[ "$SKIP_TEST" == 1 ]]; then exit 0 -- GitLab From ef83241220f5e7bf8b8408936f3b38b461ba653b Mon Sep 17 00:00:00 2001 From: Skye Wanderman-Milne Date: Tue, 16 Oct 2018 11:07:37 -0700 Subject: [PATCH 0144/1825] Make pruning work when calling cond_v2 in a v1 Graph. PiperOrigin-RevId: 217349450 --- .../python/kernel_tests/cond_v2_test.py | 6 +-- .../kernel_tests/control_flow_ops_py_test.py | 49 +++++++++++++++++++ tensorflow/python/ops/cond_v2.py | 20 +++++++- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/kernel_tests/cond_v2_test.py b/tensorflow/python/kernel_tests/cond_v2_test.py index 833a0d152c..85a5986041 100644 --- a/tensorflow/python/kernel_tests/cond_v2_test.py +++ b/tensorflow/python/kernel_tests/cond_v2_test.py @@ -138,19 +138,19 @@ class CondV2Test(test.TestCase): def testDefaultName(self): with ops.Graph().as_default(): cond = self._createCond(None) - self.assertEqual(cond.name, "cond") + self.assertStartsWith(cond.name, "cond") self.assertIn("cond_true", ops.get_default_graph()._functions) self.assertIn("cond_false", ops.get_default_graph()._functions) with ops.Graph().as_default(): with ops.name_scope("foo"): cond = self._createCond("") - self.assertEqual(cond.name, "foo/cond") + self.assertStartsWith(cond.name, "foo/cond") self.assertIn("foo_cond_true", ops.get_default_graph()._functions) self.assertIn("foo_cond_false", ops.get_default_graph()._functions) cond2 = self._createCond(None) - self.assertEqual(cond2.name, "foo/cond_1") + self.assertStartsWith(cond2.name, "foo/cond_1") self.assertIn("foo_cond_1_true", ops.get_default_graph()._functions) self.assertIn("foo_cond_1_false", ops.get_default_graph()._functions) diff --git a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py index c74fca49f8..3c7e6e6dce 100644 --- a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py +++ b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py @@ -22,6 +22,7 @@ from __future__ import print_function import collections import math +import sys import time import numpy as np @@ -740,6 +741,54 @@ class ControlFlowTest(test.TestCase): ] self.assertAllEqual(dense_gv, [0.0, 2.0]) + @test_util.run_in_graph_and_eager_modes() + def testCondAutoControlDeps(self): + + def branch_fn(): + logging_ops.print_v2("A") + logging_ops.print_v2("B") + with ops.control_dependencies([logging_ops.print_v2("C")]): + return constant_op.constant(10) + + def build_cond(): + return control_flow_ops.cond( + constant_op.constant(True), branch_fn, lambda: 0) + + def build_nested_cond(): + return control_flow_ops.cond( + constant_op.constant(True), build_cond, lambda: 0) + + # In v1 graph mode, pruning should make only "C" print. + if not context.executing_eagerly(): + with self.cached_session(): + with self.captureWritesToStream(sys.stderr) as printed: + self.assertEqual(build_cond().eval(), 10) + self.assertEqual(printed.contents(), "C\n") + + with self.captureWritesToStream(sys.stderr) as printed: + self.assertEqual(build_nested_cond().eval(), 10) + self.assertEqual(printed.contents(), "C\n") + + # In defuns, all prints should execute in program order. + # This doesn't work with legacy control flow. + if control_flow_ops.ENABLE_COND_V2: + + @eager_function.defun + def cond(): + return build_cond() + + with self.captureWritesToStream(sys.stderr) as printed: + self.assertEqual(self.evaluate(cond()), 10) + self.assertEqual(printed.contents(), "A\nB\nC\n") + + @eager_function.defun + def nested_cond(): + return build_nested_cond() + + with self.captureWritesToStream(sys.stderr) as printed: + self.assertEqual(self.evaluate(nested_cond()), 10) + self.assertEqual(printed.contents(), "A\nB\nC\n") + # Microbenchmark: 256,000 iterations/s. @test_util.disable_control_flow_v2("b/116630618 (Times out)") def testWhile_1(self): diff --git a/tensorflow/python/ops/cond_v2.py b/tensorflow/python/ops/cond_v2.py index b3ae378316..bb9286210c 100644 --- a/tensorflow/python/ops/cond_v2.py +++ b/tensorflow/python/ops/cond_v2.py @@ -62,12 +62,18 @@ def cond_v2(pred, true_fn, false_fn, name="cond"): true_name = graph.unique_name(("%strue" % scope).replace("/", "_")) false_name = graph.unique_name(("%sfalse" % scope).replace("/", "_")) + # Automatic control dependencies are added in defuns, but not in v1 + # graphs. Propagate that behavior here. + add_control_dependencies = util.in_defun() + true_graph = function.func_graph_from_py_func( true_name, true_fn, [], {}, - func_graph=util.CondBranchFuncGraph(true_name)) + func_graph=util.CondBranchFuncGraph(true_name), + add_control_dependencies=add_control_dependencies) false_graph = function.func_graph_from_py_func( false_name, false_fn, [], {}, - func_graph=util.CondBranchFuncGraph(false_name)) + func_graph=util.CondBranchFuncGraph(false_name), + add_control_dependencies=add_control_dependencies) _check_same_outputs(true_graph, false_graph) # Add inputs to true_graph and false_graph to make them match. Note that @@ -120,6 +126,16 @@ def cond_v2(pred, true_fn, false_fn, name="cond"): attr_value_pb2.AttrValue(b=True)) # pylint: enable=protected-access + # Return identities for each output of the If op, rather than the output of + # the If op directly. This makes pruning work if the output of cond() is + # fetched: the lowering pass converts the If outputs into IdentityN outputs, + # which if fetched will cause all ops in the taken branch to be run (since + # it takes all merge ops as input). After lowering, each output identity op + # will end up with only the appropriate merge op as input. + # TODO(b/79984175): this doesn't have to be a tuple once we covert to the + # correct output structure + tensors = tuple(array_ops.identity(t) for t in tensors) + result = tuple(tensors[:num_cond_outputs]) if len(result) == 1: return result[0] -- GitLab From 2acbbe480b1de8f1bc4936d272333fd812382198 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Tue, 16 Oct 2018 11:09:30 -0700 Subject: [PATCH 0145/1825] Adding GetNextAsOptional support for MultiDeviceIterator PiperOrigin-RevId: 217349782 --- .../multi_device_iterator_test.py | 69 +++++++++++++++++++ tensorflow/python/data/ops/BUILD | 1 + .../data/ops/multi_device_iterator_ops.py | 11 +++ 3 files changed, 81 insertions(+) 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 1cf6dd1bea..758d75650c 100644 --- a/tensorflow/python/data/kernel_tests/multi_device_iterator_test.py +++ b/tensorflow/python/data/kernel_tests/multi_device_iterator_test.py @@ -113,6 +113,39 @@ class MultiDeviceIteratorTest(test_base.DatasetTestBase): sess.run(elem_on_1) sess.run(elem_on_2) + def testGetNextAsOptional(self): + dataset = dataset_ops.Dataset.range(9) + multi_device_iterator = multi_device_iterator_ops.MultiDeviceIterator( + dataset, ["/cpu:1", "/cpu:2"]) + elem_on_1, elem_on_2 = multi_device_iterator.get_next_as_optional() + elem_on_1_has_value_t = elem_on_1.has_value() + elem_on_1_t = elem_on_1.get_value() + elem_on_2_has_value_t = elem_on_2.has_value() + elem_on_2_t = elem_on_2.get_value() + + config = config_pb2.ConfigProto(device_count={"CPU": 3}) + with self.test_session(config=config) as sess: + sess.run(multi_device_iterator.initializer) + for i in range(0, 8, 2): + elem_on_1_has_value, elem_on_1_value = sess.run( + [elem_on_1_has_value_t, elem_on_1_t]) + self.assertTrue(elem_on_1_has_value) + self.assertEqual(i, elem_on_1_value) + elem_on_2_has_value, elem_on_2_value = sess.run( + [elem_on_2_has_value_t, elem_on_2_t]) + self.assertTrue(elem_on_2_has_value) + self.assertEqual(i + 1, elem_on_2_value) + elem_on_1_has_value, elem_on_1_value = sess.run( + [elem_on_1_has_value_t, elem_on_1_t]) + self.assertTrue(elem_on_1_has_value) + self.assertEqual(8, elem_on_1_value) + self.assertFalse(sess.run(elem_on_1_has_value_t)) + self.assertFalse(sess.run(elem_on_2_has_value_t)) + with self.assertRaises(errors.InvalidArgumentError): + sess.run(elem_on_1_t) + with self.assertRaises(errors.InvalidArgumentError): + sess.run(elem_on_2_t) + def testUneven(self): dataset = dataset_ops.Dataset.range(10) multi_device_iterator = multi_device_iterator_ops.MultiDeviceIterator( @@ -186,6 +219,42 @@ class MultiDeviceIteratorTest(test_base.DatasetTestBase): sess.run(elem_on_1) sess.run(elem_on_2) + def testGetNextAsOptionalGpu(self): + if not test_util.is_gpu_available(): + self.skipTest("No GPU available") + + dataset = dataset_ops.Dataset.range(9) + multi_device_iterator = multi_device_iterator_ops.MultiDeviceIterator( + dataset, ["/cpu:1", "/gpu:0"]) + elem_on_1, elem_on_2 = multi_device_iterator.get_next_as_optional() + elem_on_1_has_value_t = elem_on_1.has_value() + elem_on_1_t = elem_on_1.get_value() + elem_on_2_has_value_t = elem_on_2.has_value() + elem_on_2_t = elem_on_2.get_value() + + config = config_pb2.ConfigProto(device_count={"CPU": 2, "GPU": 1}) + with self.test_session(config=config) as sess: + sess.run(multi_device_iterator.initializer) + for i in range(0, 8, 2): + elem_on_1_has_value, elem_on_1_value = sess.run( + [elem_on_1_has_value_t, elem_on_1_t]) + self.assertTrue(elem_on_1_has_value) + self.assertEqual(i, elem_on_1_value) + elem_on_2_has_value, elem_on_2_value = sess.run( + [elem_on_2_has_value_t, elem_on_2_t]) + self.assertTrue(elem_on_2_has_value) + self.assertEqual(i + 1, elem_on_2_value) + elem_on_1_has_value, elem_on_1_value = sess.run( + [elem_on_1_has_value_t, elem_on_1_t]) + self.assertTrue(elem_on_1_has_value) + self.assertEqual(8, elem_on_1_value) + self.assertFalse(sess.run(elem_on_1_has_value_t)) + self.assertFalse(sess.run(elem_on_2_has_value_t)) + with self.assertRaises(errors.InvalidArgumentError): + sess.run(elem_on_1_t) + with self.assertRaises(errors.InvalidArgumentError): + sess.run(elem_on_2_t) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/data/ops/BUILD b/tensorflow/python/data/ops/BUILD index 76bf2470b1..84f6c30f5e 100644 --- a/tensorflow/python/data/ops/BUILD +++ b/tensorflow/python/data/ops/BUILD @@ -90,6 +90,7 @@ py_library( srcs_version = "PY2AND3", deps = [ ":dataset_ops", + ":iterator_ops", "//tensorflow/python:array_ops", "//tensorflow/python:control_flow_ops", "//tensorflow/python:dataset_ops_gen", diff --git a/tensorflow/python/data/ops/multi_device_iterator_ops.py b/tensorflow/python/data/ops/multi_device_iterator_ops.py index b7033cc4ce..3bcd61a197 100644 --- a/tensorflow/python/data/ops/multi_device_iterator_ops.py +++ b/tensorflow/python/data/ops/multi_device_iterator_ops.py @@ -18,6 +18,7 @@ from __future__ import division from __future__ import print_function from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.data.ops import iterator_ops from tensorflow.python.data.util import nest from tensorflow.python.data.util import sparse from tensorflow.python.eager import context @@ -226,6 +227,16 @@ class MultiDeviceIterator(object): i += 1 return result + def get_next_as_optional(self): + result = [] + i = 0 + for device in self._devices: + with ops.device(device): + result.append(iterator_ops.get_next_as_optional( + self._device_iterators[i])) + i += 1 + return result + @property def initializer(self): return self._initializer -- GitLab From 7adb49638c583d6f1020c0ba6320918f34ab548f Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Tue, 16 Oct 2018 11:13:50 -0700 Subject: [PATCH 0146/1825] Disable tests in OSS until we can figure out what's wrong with them. PiperOrigin-RevId: 217350731 --- tensorflow/examples/autograph/integration_tests/BUILD | 5 ++++- tensorflow/python/autograph/pyct/common_transformers/BUILD | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tensorflow/examples/autograph/integration_tests/BUILD b/tensorflow/examples/autograph/integration_tests/BUILD index 3630b41fc8..1674d2fa88 100644 --- a/tensorflow/examples/autograph/integration_tests/BUILD +++ b/tensorflow/examples/autograph/integration_tests/BUILD @@ -22,7 +22,10 @@ py_test( "errors_test.py", ], srcs_version = "PY2AND3", - tags = ["no_windows"], + tags = [ + "no_oss", + "no_windows", + ], deps = [ "//tensorflow:tensorflow_py", ], diff --git a/tensorflow/python/autograph/pyct/common_transformers/BUILD b/tensorflow/python/autograph/pyct/common_transformers/BUILD index 5e2f8f3ac0..1106a19de1 100644 --- a/tensorflow/python/autograph/pyct/common_transformers/BUILD +++ b/tensorflow/python/autograph/pyct/common_transformers/BUILD @@ -34,6 +34,7 @@ py_test( name = "anf_test", srcs = ["anf_test.py"], srcs_version = "PY2AND3", + tags = ["no_oss"], deps = [ ":common_transformers", "//tensorflow/python:client_testlib", -- GitLab From c18e8cf8825dbf9c129dcdedb66820e4933e7b83 Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Tue, 16 Oct 2018 11:15:35 -0700 Subject: [PATCH 0147/1825] [Grappler] Remove unused outputs from specialized functions. 1. Remove function outputs that are not connected to other nodes and not in a fetch set. 2. Remap consumers to use new output positions. PiperOrigin-RevId: 217351054 --- tensorflow/core/grappler/optimizers/BUILD | 1 + .../grappler/optimizers/function_optimizer.cc | 208 ++++++++++++++++-- .../optimizers/function_optimizer_test.cc | 117 +++++++++- .../optimizers/meta_optimizer_test.cc | 121 +++++++++- tensorflow/core/grappler/utils/functions.cc | 41 ++++ tensorflow/core/grappler/utils/functions.h | 18 +- 6 files changed, 471 insertions(+), 35 deletions(-) diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD index 43a7d6a70b..c732c690fc 100644 --- a/tensorflow/core/grappler/optimizers/BUILD +++ b/tensorflow/core/grappler/optimizers/BUILD @@ -145,6 +145,7 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", + "//tensorflow/core/grappler:graph_view", "//tensorflow/core/grappler:grappler_item", "//tensorflow/core/grappler:op_types", "//tensorflow/core/grappler:utils", diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index 56364f0095..7c35cc5f72 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -29,6 +29,7 @@ limitations under the License. #include "tensorflow/core/framework/op_def.pb.h" #include "tensorflow/core/framework/versions.pb.h" #include "tensorflow/core/graph/graph_constructor.h" +#include "tensorflow/core/grappler/graph_view.h" #include "tensorflow/core/grappler/grappler_item.h" #include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/grappler/utils.h" @@ -39,6 +40,14 @@ namespace tensorflow { namespace grappler { namespace { +// WARNING: Code in this file implicitly assumes that function input and output +// arguments are plain tensors (tensor lists are not supported). Function inputs +// and outputs are always expanded to a single placeholder or output tensor. +// With this assumption, the calling node's input/output ports always match +// function input/output arguments. +// +// This is guaranteed by the implementation of MakeGrapplerFunctionItem. + // Mark functions that were created as a result of function specialization. constexpr char kGrapplerSpecializedFuncAttr[] = "_GrapplerSpecializedFunc"; @@ -80,13 +89,23 @@ string UniqueSpecializedFunctionName(const FunctionDef& func, // Specialized function instantiation type parameters, body parameters, and // const inputs. struct FunctionSpecializationSignature { + // Currently we do not support functions with tensor lists as inputs or + // outputs, so caller node input/output ports always match function + // input/output arguments. + using InputPort = int; + using OutputPort = int; + 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; + std::unordered_map const_inputs; bool operator==(const FunctionSpecializationSignature& other) const { bool equals = func_name == other.func_name && + is_in_fetch_set == other.is_in_fetch_set && + active_outputs == other.active_outputs && type_parameters == other.type_parameters && const_inputs == other.const_inputs; @@ -104,11 +123,21 @@ 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. - // Use 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()); @@ -126,8 +155,8 @@ struct FunctionSpecializationSignature { h = Hash64Combine(FastAttrValueHash(pair.second), h); } - std::map inputs(s.const_inputs.begin(), - s.const_inputs.end()); + 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); @@ -140,8 +169,15 @@ struct FunctionSpecializationSignature { struct FunctionSpecialization { string specialized_func_name; - std::unordered_set const_inputs; - std::unordered_set control_deps; + // Names of the tensors that were pushed down into the function body. + gtl::FlatSet const_inputs; + // Control dependencies of pushed down const inputs have to be attached to + // function caller node. + gtl::FlatSet control_deps; + // 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. + std::vector> output_mapping; }; class FakeCPUDevice : public Device { @@ -155,9 +191,12 @@ class FunctionOptimizerContext { explicit FunctionOptimizerContext(RewriterConfig::Toggle opt_level, const GrapplerItem& item) : graph_version_(item.graph.versions().producer()), - function_library_(OpRegistry::Global(), item.graph.library()) { + function_library_(OpRegistry::Global(), item.graph.library()), + // GraphView doesn't not modify the graph or the nodes. + graph_view_(const_cast(&item.graph)) { InitializeTrulyConstNodes(item); InitializeInlinedFunctions(opt_level, item); + InitializeFetchNodes(item); } const FunctionLibraryDefinition& function_library() const { @@ -173,6 +212,19 @@ class FunctionOptimizerContext { return flr_; } + const gtl::FlatMap>>& + output_mappings() const { + return output_mappings_; + } + + const GraphView& graph_view() const { return graph_view_; } + + const gtl::FlatSet& fetch_tensors() const { return fetch_tensors_; } + + bool IsFetchNode(const string& node_name) const { + return fetch_nodes_.find(node_name) != fetch_nodes_.end(); + } + bool IsInlinedFunction(const string& name) const { return inlined_functions_.count(name) > 0; } @@ -200,9 +252,25 @@ class FunctionOptimizerContext { specialized_functions_.emplace(sig, specialized_func); } + void AddOutputMapping(const string& func_node, + const FunctionSpecialization& specialized_func) { + output_mappings_.emplace(func_node, specialized_func.output_mapping); + } + + // Return true if we had any specialized function that changed it's output + // mapping, and it's required to update output consumers to new ports ids. + bool RequiresOutputMapping() const { + for (const auto& m1 : output_mappings_) { + for (const std::pair& m2 : m1.second) { + if (m2.first != m2.second) return true; + } + } + return false; + } + private: void InitializeTrulyConstNodes(const GrapplerItem& item) { - std::unordered_set feed_nodes; + gtl::FlatSet feed_nodes; for (const auto& feed : item.feed) { feed_nodes.insert(NodeName(feed.first)); } @@ -234,6 +302,13 @@ class FunctionOptimizerContext { } } + void InitializeFetchNodes(const GrapplerItem& item) { + for (const string& fetch : item.fetch) { + fetch_tensors_.insert(fetch); + fetch_nodes_.insert(NodeName(fetch)); + } + } + void InitializeFunctionLibraryRuntime() { if (!flr_) { Env* env = Env::Default(); @@ -269,9 +344,42 @@ class FunctionOptimizerContext { FunctionSpecializationSignature::Hash> specialized_functions_; + // GrapplerItem.fetch is a vector of tensors. + gtl::FlatSet fetch_tensors_; // format: node_name:port + gtl::FlatSet fetch_nodes_; // format: node_name + + // Output mappings that have to be applied to the graph after all functions + // are specialized (node name -> output mappings). + gtl::FlatMap>> output_mappings_; + + // Use graph view to find active outputs of the function caller nodes. + GraphView graph_view_; + TF_DISALLOW_COPY_AND_ASSIGN(FunctionOptimizerContext); }; +gtl::FlatSet GetActiveOutputs(const NodeDef& node, + const FunctionOptimizerContext& ctx, + int size_hint = 0) { + gtl::FlatSet active_outputs; + active_outputs.reserve(static_cast(size_hint)); + + // 1. Output can be consumed by the other graph node. + const auto node_fanout_edges = + ctx.graph_view().GetFanoutEdges(node, /*include_controlled_edges=*/false); + for (const GraphView::Edge& edge : node_fanout_edges) { + active_outputs.insert(edge.src.port_id); + } + + // 2. Or it can be in a fetch set. + for (const string& fetch_tensor : ctx.fetch_tensors()) { + int port = NodePositionIfSameNode(fetch_tensor, node.name()); + if (port >= 0) active_outputs.insert(port); + } + + return active_outputs; +} + bool HasTrulyConstInputs(const NodeDef& node, const FunctionOptimizerContext& ctx) { const auto is_truly_const = [&ctx](const string& input) { @@ -280,12 +388,24 @@ bool HasTrulyConstInputs(const NodeDef& node, return std::any_of(node.input().begin(), node.input().end(), is_truly_const); } +bool HasUnusedOutputs(const NodeDef& func_node, const FunctionDef& func, + const FunctionOptimizerContext& ctx) { + // Functions with tensor list outputs are not supported right now, so the + // 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 = + GetActiveOutputs(func_node, ctx, /*size_hind*/ num_outputs); + + return active_outputs.size() != num_outputs; +} + // Return trimmed FunctionDefLibrary with functions that are reachable from // the optimized graph. FunctionDefLibrary TrimFunctionLibrary(const FunctionLibraryDefinition& flib, const GraphDef& optimized_graph) { // Functions that are reachable from the optimized graph. - std::unordered_set keep_funcs; + gtl::FlatSet keep_funcs; std::vector func_queue; func_queue.reserve(flib.num_functions()); @@ -365,8 +485,8 @@ FunctionDefLibrary TrimFunctionLibrary(const FunctionLibraryDefinition& flib, Status PushDownConstInputs(const NodeDef& func_node, const FunctionOptimizerContext& ctx, GrapplerFunctionItem* item, - std::unordered_set* const_inputs, - std::unordered_set* control_deps) { + gtl::FlatSet* const_inputs, + gtl::FlatSet* 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) { @@ -397,8 +517,8 @@ Status PushDownConstInputs(const NodeDef& func_node, // Remove inputs that were pushed into the function body, and attach their // control dependencies to the function caller node. -void RemovePushedDownConstInputs(const std::unordered_set& const_inputs, - const std::unordered_set& control_deps, +void RemovePushedDownConstInputs(const gtl::FlatSet& const_inputs, + const gtl::FlatSet& control_deps, NodeDef* specialized_func_node) { // Nothing to do if it was no const inputs to the function node. if (const_inputs.empty()) return; @@ -416,7 +536,7 @@ void RemovePushedDownConstInputs(const std::unordered_set& const_inputs, // Attach control dependencies of pushed down const input to the caller node. if (!control_deps.empty()) { - std::unordered_set existing_control_deps; + gtl::FlatSet existing_control_deps; for (const string& input : keep_inputs) { existing_control_deps.insert(AsControlDependency(NodeName(input))); @@ -435,7 +555,12 @@ Status InitializeFunctionSpecializationSignature( const NodeDef& func_node, const FunctionDef& func, const AttrValueMap& func_attr, const FunctionOptimizerContext& ctx, FunctionSpecializationSignature* sig) { + DCHECK(sig->const_inputs.empty()); + DCHECK(sig->active_outputs.empty()); + sig->func_name = func.signature().name(); + sig->is_in_fetch_set = ctx.IsFetchNode(func_node.name()); + sig->active_outputs = GetActiveOutputs(func_node, ctx); TF_RETURN_IF_ERROR( InstantiationTypeParameters(func, func_attr, &sig->type_parameters)); @@ -484,6 +609,8 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, already_specialized->control_deps, specialized_func_node); + ctx->AddOutputMapping(specialized_func_node->name(), *already_specialized); + return Status::OK(); } @@ -498,11 +625,19 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, // Push const inputs into the function body, and keep track of their control // dependencies. - std::unordered_set const_inputs; - std::unordered_set control_deps; + gtl::FlatSet const_inputs; + gtl::FlatSet control_deps; TF_RETURN_IF_ERROR(PushDownConstInputs(func_node, *ctx, &item, &const_inputs, &control_deps)); + // Remove function outputs that do not have any consumers. We can't safely + // 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)); + } + // TODO(ezhulenev): Push down known input shapes. FunctionDef specialized_func; TF_RETURN_IF_ERROR(MakeFunctionDef(item, flib, &specialized_func)); @@ -528,8 +663,10 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, RemovePushedDownConstInputs(const_inputs, control_deps, specialized_func_node); - ctx->AddSpecializedFunction( - signature, {specialized_func_name, const_inputs, control_deps}); + FunctionSpecialization func_specialization = { + specialized_func_name, const_inputs, control_deps, output_mapping}; + ctx->AddSpecializedFunction(signature, func_specialization); + ctx->AddOutputMapping(specialized_func_node->name(), func_specialization); return Status::OK(); } @@ -835,9 +972,12 @@ Status FunctionOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, // Do not specialize if function has custom gradient. const string grad_func = ctx.function_library().FindGradient(func_name); - // 2b. Specialize it to it's instantiation context if can't be inlined. - if (specialize_func && grad_func.empty() && - (IsParametrized(*func) || HasTrulyConstInputs(node, ctx))) { + // 2b. Specialize it to it's instantiation context if can't be inlined, + // and it has something worth specializing. + bool specialization_worthy = IsParametrized(*func) || + HasTrulyConstInputs(node, ctx) || + HasUnusedOutputs(node, *func, ctx); + if (specialize_func && grad_func.empty() && specialization_worthy) { // TODO(ezhulenev): Specialize function call if input has a known shape. // Specialize function body for its instantiation attributes and inputs. TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( @@ -854,6 +994,32 @@ Status FunctionOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, #undef TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED } + // Function specialization might change the number of function outputs, so we + // have to process the final optimized graph and update all the node mapping. + if (ctx.RequiresOutputMapping()) { + GraphView optimized_graph_view(optimized_graph); + for (const auto& output_mapping : ctx.output_mappings()) { + const auto& node_name = output_mapping.first; + const auto& mappings = output_mapping.second; + + for (const std::pair& mapping : mappings) { + int from = mapping.first; + int to = mapping.second; + + // Find the output port corresponding to the old output position. + GraphView::OutputPort from_port = + optimized_graph_view.GetOutputPort(node_name, from); + + // Update all input ports that read from old output port. + for (GraphView::InputPort to_port : + optimized_graph_view.GetFanout(from_port)) { + *to_port.node->mutable_input(to_port.port_id) = + strings::StrCat(node_name, ":", to); + } + } + } + } + *optimized_graph->mutable_versions() = item.graph.versions(); *optimized_graph->mutable_library() = options_.enable_trim_function_library diff --git a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc index fab3f994c1..a22f97800f 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/core/grappler/op_types.h" #include "tensorflow/core/grappler/utils/grappler_test.h" #include "tensorflow/core/lib/core/status_test_util.h" +#include "tensorflow/core/lib/gtl/flatset.h" namespace tensorflow { namespace grappler { @@ -856,6 +857,10 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_OncePerUniqueContext) { NDef("mul_6", "MyMul", {"three", "xf"}, {{"T", DT_FLOAT}}, kDevice)}, function_library); + // Specify fetch nodes before optimization to prevent pruning unused function + // outputs. + item.fetch = {"mul_1", "mul_2", "mul_3", "mul_4", "mul_5", "mul_6"}; + GraphDef output; TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &output)); @@ -893,8 +898,9 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_OncePerUniqueContext) { EXPECT_EQ("MyMul_specialized_for_mul_4", node.op()); ASSERT_EQ(3, node.input_size()); EXPECT_EQ("yf", node.input(0)); - EXPECT_EQ("^init", node.input(1)); - EXPECT_EQ("^xf", node.input(2)); + gtl::FlatSet expected_ctrl = {"^init", "^xf"}; + gtl::FlatSet actual_ctrl = {node.input(1), node.input(2)}; + EXPECT_EQ(expected_ctrl, actual_ctrl); } else if (node.name() == "mul_6" && count++) { EXPECT_EQ("MyMul_specialized_for_mul_6", node.op()); @@ -908,7 +914,6 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_OncePerUniqueContext) { // And that graph evaluation yields the same result. Tensor pi = test::AsScalar(3.14f); Tensor four = test::AsScalar(4); - item.fetch = {"mul_1", "mul_2", "mul_3", "mul_4", "mul_5", "mul_6"}; item.feed = {{"xf", pi}, {"yf", pi}, {"xi", four}, {"yi", four}}; auto tensors_expected = EvaluateFetchNodes(item); @@ -923,6 +928,112 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_OncePerUniqueContext) { test::ExpectTensorEqual(tensors_expected[5], tensors[5]); } +TEST_F(FunctionOptimizerTest, SpecializeFunctionForUsedOutputTensors) { + using test::function::NDef; + + FunctionOptimizer optimizer(RewriterConfig::DEFAULT); + + // MyFunc computes x*y three times and has three output values. + FunctionDef my_func = FunctionDefHelper::Create( + "MyFunc", {"x:T", "y:T"}, {"z1:T", "z2:T", "z3:T"}, {"T: {float, int32}"}, + {{{"output1"}, "Mul", {"x", "y"}, {{"T", "$T"}}}, + {{"output2"}, "Mul", {"x", "y"}, {{"T", "$T"}}}, + {{"output3"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z1", "output1:z:0"}, {"z2", "output2:z:0"}, {"z3", "output3:z:0"}}); + (*my_func.mutable_attr())["_noinline"].set_b(true); + std::vector function_library = {my_func}; + + GrapplerItem item; + item.graph = test::function::GDef( + {NDef("init", "NoOp", {}, {}, kDevice), + + // Float placeholders. + NDef("xf", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + NDef("yf", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + + // Specialization #1: DT_FLOAT type parameter. All outputs used. + NDef("fn1", "MyFunc", {"xf", "yf"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn1_0", "Identity", {"fn1:0"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn1_1", "Identity", {"fn1:1"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn1_2", "Identity", {"fn1:2"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #2: DT_FLOAT type parameter. Only first output used. + NDef("fn2", "MyFunc", {"xf", "yf"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn2_0", "Identity", {"fn2:0"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #3: DT_FLOAT type parameter. Only second output used. + NDef("fn3", "MyFunc", {"xf", "yf"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn3_1", "Identity", {"fn3:1"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #4: DT_FLOAT type parameter. Only last output used. + NDef("fn4", "MyFunc", {"xf", "yf"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn4_2", "Identity", {"fn4:2"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #5: DT_FLOAT type parameter. First and last outputs. + NDef("fn5", "MyFunc", {"xf", "yf"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn5_0", "Identity", {"fn5:0"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn5_2", "Identity", {"fn5:2"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #6: DT_FLOAT type parameter. Outputs not used. + // Check that function optimizer do not fail. In practice it should be + // pruned from the graph before passing to function optimizer. + NDef("fn6", "MyFunc", {"xf", "yf"}, {{"T", DT_FLOAT}}, kDevice)}, + function_library); + + GraphDef output; + TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &output)); + + // Make sure that MyFunc was specialized once per unique context. + EXPECT_EQ(6, output.library().function_size()); + + // And graph nodes calling specialized functions. + int found = 0; + for (const NodeDef& node : output.node()) { + // All function caller nodes must be specialized. + if (node.name() == "fn1" && found++) { + EXPECT_EQ("MyFunc_specialized_for_fn1", node.op()); + } else if (node.name() == "fn2" && found++) { + EXPECT_EQ("MyFunc_specialized_for_fn2", node.op()); + } else if (node.name() == "fn3" && found++) { + EXPECT_EQ("MyFunc_specialized_for_fn3", node.op()); + } else if (node.name() == "fn4" && found++) { + EXPECT_EQ("MyFunc_specialized_for_fn4", node.op()); + } else if (node.name() == "fn5" && found++) { + EXPECT_EQ("MyFunc_specialized_for_fn5", node.op()); + } else if (node.name() == "fn6" && found++) { + EXPECT_EQ("MyFunc_specialized_for_fn6", node.op()); + } + // And all consumers of specialized function nodes must be mapped to new + // output ports. + if (node.name() == "use_fn3_1" && found++) { + EXPECT_EQ("fn3:0", node.input(0)); + } else if (node.name() == "use_fn4_2" && found++) { + EXPECT_EQ("fn4:0", node.input(0)); + } else if (node.name() == "use_fn5_0" && found++) { + EXPECT_EQ("fn5:0", node.input(0)); + } else if (node.name() == "use_fn5_2" && found++) { + EXPECT_EQ("fn5:1", node.input(0)); + } + } + EXPECT_EQ(10, found); + + // And that graph evaluation yields the same result. + Tensor pi = test::AsScalar(3.14f); + item.fetch = {"use_fn1_0", "use_fn1_1", "use_fn1_2", "use_fn2_0", + "use_fn3_1", "use_fn4_2", "use_fn5_0", "use_fn5_2"}; + item.feed = {{"xf", pi}, {"yf", pi}}; + + auto tensors_expected = EvaluateFetchNodes(item); + GrapplerItem optimized(item, std::move(output)); + auto tensors = EvaluateFetchNodes(optimized); + + ASSERT_EQ(tensors_expected.size(), tensors.size()); + for (int i = 0; i < item.fetch.size(); ++i) { + test::ExpectTensorEqual(tensors_expected[i], tensors[i]); + } +} + TEST_F(FunctionOptimizerTest, PruningUselessLibraryFunctions) { using test::function::NDef; FunctionOptimizer optimizer(RewriterConfig::DEFAULT); diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc b/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc index 79a0726597..e15b9e12f8 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc @@ -285,28 +285,30 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { output.library()); // Specialized and optimized functions should be added to the graph. - EXPECT_EQ(5, optimized_flib.num_functions()); + EXPECT_EQ(6, optimized_flib.num_functions()); // MyQuadratic should be specialized once: // 0. 'quadratic' node in the main graph const string optimized_0 = "MyQuadratic_specialized_for_quadratic"; // MySquare should be specialized and optimized for 3 instantiations: - // 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 + // 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) const string optimized_1 = "MySquare_specialized_for_square"; const string optimized_2 = "MySquare_specialized_for_square_1"; + const string optimized_3 = "MySquare_specialized_for_quadratic"; 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; @@ -325,14 +327,13 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { if (node.name() == "square" && count++) { EXPECT_EQ(optimized_2, node.op()); } else if (node.name() == "quadratic" && count++) { - // Share specialized function with the 'square' node. - EXPECT_EQ(optimized_2, node.op()); + EXPECT_EQ(optimized_3, node.op()); } } EXPECT_EQ(2, count); - const std::vector optimized_funcs = {optimized_func_1, - optimized_func_2}; + const std::vector optimized_funcs = { + optimized_func_1, optimized_func_2, optimized_func_3}; // MyMul should be inlined into all optimized versions of MySquare. for (const FunctionDef* optimized_func : optimized_funcs) { @@ -378,6 +379,108 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { test::ExpectTensorEqual(tensors_expected[1], tensors[1]); } +TEST_F(MetaOptimizerTest, OptimizeFunctionLibraryPruneFunctionBody) { + using test::function::NDef; + + // Enable function optimization and pruning. + RewriterConfig rewriter_config; + rewriter_config.set_meta_optimizer_iterations(RewriterConfig::TWO); + rewriter_config.set_function_optimization(RewriterConfig::ON); + rewriter_config.add_optimizers("function"); + rewriter_config.add_optimizers("pruning"); + rewriter_config.set_min_graph_nodes(-1); + + MetaOptimizer optimizer(nullptr, rewriter_config); + + // MyFunc defines two Mul nodes inside function body and two corresponding + // function outputs. + FunctionDef my_func = FunctionDefHelper::Create( + "MyFunc", {"x:T", "y:T"}, {"z1:T", "z2:T"}, {"T: {float, double}"}, + {{{"mul1"}, "Mul", {"x", "y"}, {{"T", "$T"}}}, + {{"mul2"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z1", "mul1:z:0"}, {"z2", "mul2:z:0"}}); + (*my_func.mutable_attr())["_noinline"].set_b(true); + + // Tensorflow graph: + // + // a = tf.Placeholder(tf.float); + // b = tf.Placeholder(tf.int32); + // + // fn1 = MyFunc(a, b); + // fn2 = MyFunc(a, b); + // + // Fetch: fn1:0 and fn2:1 via Identity nodes. + GrapplerItem item; + item.graph = test::function::GDef( + {NDef("a", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + NDef("b", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + // Calls into function library + NDef("fn1", "MyFunc", {"a", "b"}, {{"T", DT_FLOAT}}, kDevice), + NDef("fn2", "MyFunc", {"a", "b"}, {{"T", DT_FLOAT}}, kDevice), + // Read outputs of function call nodes + NDef("out_fn1", "Identity", {"fn1:0"}, {{"T", DT_FLOAT}}, kDevice), + NDef("out_fn2", "Identity", {"fn2:1"}, {{"T", DT_FLOAT}}, kDevice)}, + // FunctionLib + {my_func}); + + GraphDef output; + TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &output)); + + FunctionLibraryDefinition optimized_flib(OpRegistry::Global(), + output.library()); + + // Specialized and optimized functions should be added to the graph. + EXPECT_EQ(2, optimized_flib.num_functions()); + + // Expected names of the specialized and optimized functions. + const string optimized_fn1 = "MyFunc_specialized_for_fn1"; + const string optimized_fn2 = "MyFunc_specialized_for_fn2"; + + const FunctionDef* optimized_func_fn1 = optimized_flib.Find(optimized_fn1); + const FunctionDef* optimized_func_fn2 = optimized_flib.Find(optimized_fn2); + + ASSERT_NE(optimized_func_fn1, nullptr); + ASSERT_NE(optimized_func_fn2, nullptr); + + // Graph should call optimized function. + int count = 0; + for (const NodeDef& node : output.node()) { + if (node.name() == "fn1" && count++) { + EXPECT_EQ(optimized_fn1, node.op()); + } else if (node.name() == "fn2" && count++) { + EXPECT_EQ(optimized_fn2, node.op()); + } + } + EXPECT_EQ(2, count); + + // Specialized MyFuncs should have just one Mul node and single output arg. + + // 1. Specialized for fn1:0. + ASSERT_EQ(1, optimized_func_fn1->node_def_size()); + EXPECT_EQ(1, optimized_func_fn1->signature().output_arg_size()); + EXPECT_EQ("z1", optimized_func_fn1->signature().output_arg(0).name()); + EXPECT_EQ("mul1", optimized_func_fn1->node_def(0).name()); + + // 2. Specialized for fn2:1. + ASSERT_EQ(1, optimized_func_fn2->node_def_size()); + EXPECT_EQ(1, optimized_func_fn2->signature().output_arg_size()); + EXPECT_EQ("z2", optimized_func_fn2->signature().output_arg(0).name()); + EXPECT_EQ("mul2", optimized_func_fn2->node_def(0).name()); + + // Verify that output tensors are equal. + item.fetch = {"out_fn1", "out_fn2"}; + item.feed.emplace_back("a", test::AsScalar(2.0f)); + item.feed.emplace_back("b", test::AsScalar(3.123f)); + auto tensors_expected = EvaluateFetchNodes(item); + + GrapplerItem optimized(item, std::move(output)); + auto tensors = EvaluateFetchNodes(optimized); + + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); + test::ExpectTensorEqual(tensors_expected[1], tensors[1]); +} + TEST_F(MetaOptimizerTest, OptimizeFunctionLibraryWithRestrictions) { using test::function::NDef; using FDH = FunctionDefHelper; diff --git a/tensorflow/core/grappler/utils/functions.cc b/tensorflow/core/grappler/utils/functions.cc index 6861fb423c..bfb5a2ad84 100644 --- a/tensorflow/core/grappler/utils/functions.cc +++ b/tensorflow/core/grappler/utils/functions.cc @@ -685,6 +685,47 @@ Status ReplaceInputWithConst(const NodeDef& input_const, int input_position, return Status::OK(); } +Status RemoveUnusedOutputs(const gtl::FlatSet& active_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()) { + return errors::InvalidArgument( + "Active output position is out of bound: active_output=", + active_output, " num_output_args=", item->output_size()); + } + } + + gtl::FlatSet unused_output_args; + + const auto is_unused_output_arg = [&](const OutputArgExpansion& output) { + return unused_output_args.find(&output) != unused_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()) { + // Add output mapping only if output position changed. + output_mapping->push_back({i, i - unused_output_args.size()}); + } + } + + auto& o = item->output_arg_expansions_; + o.erase(std::remove_if(o.begin(), o.end(), is_unused_output_arg), o.end()); + + return Status::OK(); +} + Status MakeFunctionDef(const GrapplerFunctionItem& item, const FunctionLibraryDefinition& flib, FunctionDef* func) { diff --git a/tensorflow/core/grappler/utils/functions.h b/tensorflow/core/grappler/utils/functions.h index ef944ced09..dc8c3f1d11 100644 --- a/tensorflow/core/grappler/utils/functions.h +++ b/tensorflow/core/grappler/utils/functions.h @@ -25,6 +25,7 @@ limitations under the License. #include "tensorflow/core/framework/node_def_util.h" #include "tensorflow/core/framework/op_def.pb.h" #include "tensorflow/core/grappler/grappler_item.h" +#include "tensorflow/core/lib/gtl/flatset.h" namespace tensorflow { namespace grappler { @@ -167,6 +168,9 @@ class GrapplerFunctionItem : public GrapplerItem { private: friend Status ReplaceInputWithConst(const NodeDef&, int, GrapplerFunctionItem*); + friend Status RemoveUnusedOutputs( + const gtl::FlatSet& active_outputs, GrapplerFunctionItem* item, + std::vector>* output_mapping); string description_; AttrValueMap func_attr_; // Attributes specific to function definition that @@ -216,13 +220,23 @@ Status RegisterGrapplerFunctionConnectivity( Status ReplaceInputWithConst(const NodeDef& input_const, int input_position, 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 gtl::FlatSet& active_outputs, + GrapplerFunctionItem* item, + std::vector>* output_mapping); + // Make a GrapplerFunctionItem from the function definition and function // instantiation attributes (caller node attributes). Returns error if the given // function def cannot be converted (e.g. not all attributes are defined). Status MakeGrapplerFunctionItem(const FunctionDef& func, const AttrValueMap& func_instantiation_attr, const FunctionLibraryDefinition& flib, - const int graph_def_version, + int graph_def_version, GrapplerFunctionItem* item); // Make a GrapplerFunction item from the function definition. Function must be @@ -232,7 +246,7 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, // without specializing it to it's instantiation attributes (at least types)? Status MakeGrapplerFunctionItem(const FunctionDef& func, const FunctionLibraryDefinition& flib, - const int graph_def_version, + int graph_def_version, GrapplerFunctionItem* item); // Make a FunctionDef from the GrapplerFunctionItem. Use function library -- GitLab From efd1fb3c20a16a9a65c9186fa756944fad73635e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 11:16:08 -0700 Subject: [PATCH 0148/1825] This CL changes the defun descriptor to create a separate polymorphic function for each instance. PiperOrigin-RevId: 217351144 --- tensorflow/python/eager/function.py | 34 +++++++++++++++- tensorflow/python/eager/function_test.py | 49 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index e8d5416245..6b37ab9410 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -1077,6 +1077,10 @@ class PolymorphicFunction(object): 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. + self._descriptor_cache = weakref.WeakKeyDictionary() fullargspec = tf_inspect.getfullargspec(self._python_function) if tf_inspect.ismethod(self._python_function): @@ -1151,8 +1155,34 @@ class PolymorphicFunction(object): # foo = Foo() # foo.bar() # `foo.bar` is a `PolymorphicFunction` instance # - # then `instance` will be `foo` (and `owner` will be `Foo`). - return functools.partial(self.__call__, instance) + # then `instance` will be `foo` (and `owner` will be `Foo`). We create a + # new instance of PolymorphicFunction 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). + def make_partial_py_func(py_func, weak_instance): + return lambda *args, **kwargs: py_func(weak_instance(), *args, **kwargs) + weak_instance = weakref.ref(instance) + instance_func = PolymorphicFunction( + make_partial_py_func(self.python_function, weak_instance), + name=self._name) + + # And we wrap the function with tf_decorator so inspection works correctly + wrapped_instance_func = tf_decorator.make_decorator( + self.python_function, instance_func) + + # And finally add the wrapped function to the description cache + self._descriptor_cache[instance] = wrapped_instance_func + + # Return the cached polymorphic function for the instance + return self._descriptor_cache[instance] def _cache_key(self, args, kwargs): """Computes the cache key given inputs and execution context.""" diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 4f947e91bf..2d75b2c246 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -60,6 +60,7 @@ from tensorflow.python.training import momentum from tensorflow.python.training import training_ops from tensorflow.python.util import compat from tensorflow.python.util import nest +from tensorflow.python.util import tf_inspect class MiniModel(keras_training.Model): @@ -2338,6 +2339,54 @@ class FunctionTest(test.TestCase): self.assertEqual(len(maybe_add._function_cache), 3) self.assertEqual(len(add._function_cache), 2) + def testDecoratedMethod(self): + m = DefunnedMiniModel() + instance_call_one = m.call(array_ops.ones([1, 2]), training=True) + instance_call_two = m.call( + inputs=array_ops.ones([1, 2]), training=True) + class_call = DefunnedMiniModel.call(m, array_ops.ones([1, 2]), + training=True) + self.assertAllEqual(instance_call_one, instance_call_two) + self.assertAllEqual(instance_call_one, class_call) + + def testDecoratedMethodUniquePolymorphicFuncPerInstance(self): + m = DefunnedMiniModel() + n = DefunnedMiniModel() + + class_method_one = DefunnedMiniModel.call + class_method_two = DefunnedMiniModel.call + + m_method_one = m.call + m_method_two = m.call + + n_method_one = n.call + n_method_two = n.call + + self.assertEqual(class_method_one, class_method_two) + self.assertEqual(m_method_one, m_method_two) + self.assertEqual(n_method_one, n_method_two) + self.assertNotEqual(m.call, n.call) + + def testDecoratedMethodInspect(self): + m = DefunnedMiniModel() + fullargspec = tf_inspect.getfullargspec(m.call) + self.assertTrue('training' in fullargspec.args) + + def testDecoratedMethodGetConcreteFunction(self): + m = DefunnedMiniModel() + instance_call_one = m.call.get_concrete_function( + array_ops.ones([1, 2]), training=False) + instance_call_two = m.call.get_concrete_function( + inputs=array_ops.ones([1, 2]), training=False) + self.assertAllEqual(instance_call_one(array_ops.ones([1, 2])), + instance_call_two(array_ops.ones([1, 2]))) + + # Also make sure get_concrete_function works on the class method + DefunnedMiniModel.call.get_concrete_function( + m, array_ops.ones([1, 2]), training=False) + DefunnedMiniModel.call.get_concrete_function( + m, inputs=array_ops.ones([1, 2]), training=True) + @test_util.with_c_shapes class AutomaticControlDependenciesTest(test.TestCase): -- GitLab From 8d6afba2a8882e2f5abf2d7790855caf2725e42b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 11:23:52 -0700 Subject: [PATCH 0149/1825] Update ops-related pbtxt files. PiperOrigin-RevId: 217352683 --- tensorflow/core/ops/compat/ops_history.v1.pbtxt | 12 ++++++++++++ tensorflow/core/ops/ops.pbtxt | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt index 63d037c743..ed4c3f9a62 100644 --- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt +++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt @@ -31299,6 +31299,18 @@ op { type: DT_STRING } } +op { + name: "MatchingFilesDataset" + input_arg { + name: "patterns" + type: DT_STRING + } + output_arg { + name: "handle" + type: DT_VARIANT + } + is_stateful: true +} op { name: "MatrixBandPart" input_arg { diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt index 9a566c9d84..3b898f2155 100644 --- a/tensorflow/core/ops/ops.pbtxt +++ b/tensorflow/core/ops/ops.pbtxt @@ -15814,6 +15814,18 @@ op { type: DT_STRING } } +op { + name: "MatchingFilesDataset" + input_arg { + name: "patterns" + type: DT_STRING + } + output_arg { + name: "handle" + type: DT_VARIANT + } + is_stateful: true +} op { name: "MatrixBandPart" input_arg { -- GitLab From 91286db9454ff73807f38fadb6f41eec7cee2bdf Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 11:30:16 -0700 Subject: [PATCH 0150/1825] Handle comma separated list of placholder types in 'optimize_for_inference.py' tool. PiperOrigin-RevId: 217353860 --- .../python/tools/optimize_for_inference.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tensorflow/python/tools/optimize_for_inference.py b/tensorflow/python/tools/optimize_for_inference.py index dac6a06a89..fbf8c2d709 100644 --- a/tensorflow/python/tools/optimize_for_inference.py +++ b/tensorflow/python/tools/optimize_for_inference.py @@ -88,7 +88,7 @@ def main(unused_args): input_graph_def, FLAGS.input_names.split(","), FLAGS.output_names.split(","), - FLAGS.placeholder_type_enum, + _parse_placeholder_types(FLAGS.placeholder_type_enum), FLAGS.toco_compatible) if FLAGS.frozen_graph: @@ -101,6 +101,12 @@ def main(unused_args): return 0 +def _parse_placeholder_types(values): + """Extracts placeholder types from a comma separate list.""" + values = [int(value) for value in values.split(",")] + return values if len(values) > 1 else values[0] + + def parse_args(): """Parses command line arguments.""" parser = argparse.ArgumentParser() @@ -137,9 +143,12 @@ def parse_args(): """) parser.add_argument( "--placeholder_type_enum", - type=int, - default=dtypes.float32.as_datatype_enum, - help="The AttrValue enum to use for placeholders.") + type=str, + default=str(dtypes.float32.as_datatype_enum), + help="""\ + The AttrValue enum to use for placeholders. + Or a comma separated list, one value for each placeholder.\ + """) parser.add_argument( "--toco_compatible", type=bool, -- GitLab From eb428951c53790ba6d424eac20111dce1d64599c Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Tue, 16 Oct 2018 11:40:40 -0700 Subject: [PATCH 0151/1825] Automated rollback of commit 23df2724a7f5ed2d58b5090de4d525db39838da2 PiperOrigin-RevId: 217355812 --- .../python/autograph/converters/call_trees.py | 19 +++++++++++----- .../python/autograph/pyct/inspect_utils.py | 22 +++++-------------- .../pyct/static_analysis/live_values.py | 16 ++++++++++---- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/tensorflow/python/autograph/converters/call_trees.py b/tensorflow/python/autograph/converters/call_trees.py index 0170173e61..ca6945266e 100644 --- a/tensorflow/python/autograph/converters/call_trees.py +++ b/tensorflow/python/autograph/converters/call_trees.py @@ -308,12 +308,7 @@ class CallTreeTransformer(converter.Base): target_fqn = anno.getanno(node.func, 'fqn') else: target_fqn = None - - if inspect_utils.isbuiltin(target_entity): - # Note: Any builtin that passed the builtins converter is assumed to be - # safe for graph mode. - return node - elif self._function_is_compilable(target_entity): + if self._function_is_compilable(target_entity): node = self._rename_compilable_function(node) elif target_fqn and target_fqn in KNOWN_NUMPY_FUNCTIONS: # TODO(mdan): Should we replace these with equivalent TF ops instead? @@ -323,6 +318,18 @@ class CallTreeTransformer(converter.Base): raise NotImplementedError( 'py_func with return values (unknown function)') else: + if anno.hasanno(node.func, anno.Basic.QN): + # Special-case a few builtins that otherwise go undetected. This + # normally doesn't pose a problem, but the dict built-in doesn't + # work with inspect.getargspec which is required for dynamic functions. + # Note: expecting this is resilient to aliasing (e.g. + # dict = an_evil_dict), because in those cases the regular mechanisms + # process a simple user function. + qn = anno.getanno(node.func, anno.Basic.QN) + # Add items to this list as needed. + if str(qn) in ('dict',): + return node + if ast_util.matches(node, 'super(_)'): # super() calls are preserved. The class conversion mechanism will # ensure that they return the correct value. diff --git a/tensorflow/python/autograph/pyct/inspect_utils.py b/tensorflow/python/autograph/pyct/inspect_utils.py index 6d5cced0ac..a09d481003 100644 --- a/tensorflow/python/autograph/pyct/inspect_utils.py +++ b/tensorflow/python/autograph/pyct/inspect_utils.py @@ -29,25 +29,15 @@ import six from tensorflow.python.util import tf_inspect -# These functions test negative for isinstance(*, types.BuiltinFunctionType) -# and inspect.isbuiltin, and are generally not visible in globals(). -SPECIAL_BUILTINS = { - 'dict': dict, - 'float': float, - 'int': int, - 'print': print, - 'range': range, - 'tuple': tuple -} - -if six.PY2: - SPECIAL_BUILTINS['xrange'] = xrange - - def isbuiltin(f): """Returns True if the argument is a built-in function.""" - if f in SPECIAL_BUILTINS.values(): + # Note these return false for isinstance(f, types.BuiltinFunctionType) so we + # need to specifically check for them. + if f in (range, int, float): return True + if six.PY2: + if f in (xrange,): + return True if isinstance(f, types.BuiltinFunctionType): return True if tf_inspect.isbuiltin(f): diff --git a/tensorflow/python/autograph/pyct/static_analysis/live_values.py b/tensorflow/python/autograph/pyct/static_analysis/live_values.py index e8e3d229be..dc363f9a47 100644 --- a/tensorflow/python/autograph/pyct/static_analysis/live_values.py +++ b/tensorflow/python/autograph/pyct/static_analysis/live_values.py @@ -24,12 +24,21 @@ from __future__ import division from __future__ import print_function import gast +import six from tensorflow.python.autograph.pyct import anno -from tensorflow.python.autograph.pyct import inspect_utils from tensorflow.python.autograph.pyct import transformer +# TODO(aqj): Do we need this? Do other builtins fail in similar ways +# See b/114389775 for a related bug in pyct +# These symbols are legal in Python, but don't appear in the namespace. +_SPECIAL_SYMBOLS = {'range': range, 'print': print} + +if six.PY2: + _SPECIAL_SYMBOLS['xrange'] = xrange + + class LiveValueResolver(transformer.Base): """Annotates nodes with live values.""" @@ -66,11 +75,10 @@ class LiveValueResolver(transformer.Base): # If the symbol value is for example a primitive, then it will not # have a name. pass - elif node.id in inspect_utils.SPECIAL_BUILTINS: + elif node.id in _SPECIAL_SYMBOLS: # Note: if the user redefined any of these symbols, then they would # be visible in the namespace and we would never reach this branch. - anno.setanno( - node, 'live_val', inspect_utils.SPECIAL_BUILTINS[node.id]) + anno.setanno(node, 'live_val', _SPECIAL_SYMBOLS[node.id]) else: pass # TODO(mdan): Should we raise an error here? -- GitLab From 4d2d6ddb16036cb390c5dea2b47065fe584d3c1b Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 11:50:51 -0700 Subject: [PATCH 0152/1825] Go: Update generated wrapper functions for TensorFlow ops. PiperOrigin-RevId: 217357664 --- tensorflow/go/op/wrappers.go | 228 +++++++++++++++++------------------ 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 662f6f227a..6b1ddef852 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -4843,6 +4843,120 @@ func CudnnRNNParamsToCanonical(scope *Scope, num_layers tf.Output, num_units tf. return weights, biases } +// CudnnRNNBackpropV2Attr is an optional argument to CudnnRNNBackpropV2. +type CudnnRNNBackpropV2Attr func(optionalAttr) + +// CudnnRNNBackpropV2RnnMode sets the optional rnn_mode attribute to value. +// If not specified, defaults to "lstm" +func CudnnRNNBackpropV2RnnMode(value string) CudnnRNNBackpropV2Attr { + return func(m optionalAttr) { + m["rnn_mode"] = value + } +} + +// CudnnRNNBackpropV2InputMode sets the optional input_mode attribute to value. +// If not specified, defaults to "linear_input" +func CudnnRNNBackpropV2InputMode(value string) CudnnRNNBackpropV2Attr { + return func(m optionalAttr) { + m["input_mode"] = value + } +} + +// CudnnRNNBackpropV2Direction sets the optional direction attribute to value. +// If not specified, defaults to "unidirectional" +func CudnnRNNBackpropV2Direction(value string) CudnnRNNBackpropV2Attr { + return func(m optionalAttr) { + m["direction"] = value + } +} + +// CudnnRNNBackpropV2Dropout sets the optional dropout attribute to value. +// If not specified, defaults to 0 +func CudnnRNNBackpropV2Dropout(value float32) CudnnRNNBackpropV2Attr { + return func(m optionalAttr) { + m["dropout"] = value + } +} + +// CudnnRNNBackpropV2Seed sets the optional seed attribute to value. +// If not specified, defaults to 0 +func CudnnRNNBackpropV2Seed(value int64) CudnnRNNBackpropV2Attr { + return func(m optionalAttr) { + m["seed"] = value + } +} + +// CudnnRNNBackpropV2Seed2 sets the optional seed2 attribute to value. +// If not specified, defaults to 0 +func CudnnRNNBackpropV2Seed2(value int64) CudnnRNNBackpropV2Attr { + return func(m optionalAttr) { + m["seed2"] = value + } +} + +// Backprop step of CudnnRNN. +// +// Compute the backprop of both data and weights in a RNN. Takes an extra +// "host_reserved" inupt than CudnnRNNBackprop, which is used to determine RNN +// cudnnRNNAlgo_t and cudnnMathType_t. +// +// 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 +// 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. +// host_reserved: The same host_reserved 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 CudnnRNNBackpropV2(scope *Scope, input tf.Output, input_h tf.Output, input_c tf.Output, params 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 ...CudnnRNNBackpropV2Attr) (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: "CudnnRNNBackpropV2", + Input: []tf.Input{ + input, input_h, input_c, params, 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) +} + // ResourceStridedSliceAssignAttr is an optional argument to ResourceStridedSliceAssign. type ResourceStridedSliceAssignAttr func(optionalAttr) @@ -14396,120 +14510,6 @@ func ResourceApplyPowerSign(scope *Scope, var_ tf.Output, m tf.Output, lr tf.Out return scope.AddOperation(opspec) } -// CudnnRNNBackpropV2Attr is an optional argument to CudnnRNNBackpropV2. -type CudnnRNNBackpropV2Attr func(optionalAttr) - -// CudnnRNNBackpropV2RnnMode sets the optional rnn_mode attribute to value. -// If not specified, defaults to "lstm" -func CudnnRNNBackpropV2RnnMode(value string) CudnnRNNBackpropV2Attr { - return func(m optionalAttr) { - m["rnn_mode"] = value - } -} - -// CudnnRNNBackpropV2InputMode sets the optional input_mode attribute to value. -// If not specified, defaults to "linear_input" -func CudnnRNNBackpropV2InputMode(value string) CudnnRNNBackpropV2Attr { - return func(m optionalAttr) { - m["input_mode"] = value - } -} - -// CudnnRNNBackpropV2Direction sets the optional direction attribute to value. -// If not specified, defaults to "unidirectional" -func CudnnRNNBackpropV2Direction(value string) CudnnRNNBackpropV2Attr { - return func(m optionalAttr) { - m["direction"] = value - } -} - -// CudnnRNNBackpropV2Dropout sets the optional dropout attribute to value. -// If not specified, defaults to 0 -func CudnnRNNBackpropV2Dropout(value float32) CudnnRNNBackpropV2Attr { - return func(m optionalAttr) { - m["dropout"] = value - } -} - -// CudnnRNNBackpropV2Seed sets the optional seed attribute to value. -// If not specified, defaults to 0 -func CudnnRNNBackpropV2Seed(value int64) CudnnRNNBackpropV2Attr { - return func(m optionalAttr) { - m["seed"] = value - } -} - -// CudnnRNNBackpropV2Seed2 sets the optional seed2 attribute to value. -// If not specified, defaults to 0 -func CudnnRNNBackpropV2Seed2(value int64) CudnnRNNBackpropV2Attr { - return func(m optionalAttr) { - m["seed2"] = value - } -} - -// Backprop step of CudnnRNN. -// -// Compute the backprop of both data and weights in a RNN. Takes an extra -// "host_reserved" inupt than CudnnRNNBackprop, which is used to determine RNN -// cudnnRNNAlgo_t and cudnnMathType_t. -// -// 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 -// 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. -// host_reserved: The same host_reserved 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 CudnnRNNBackpropV2(scope *Scope, input tf.Output, input_h tf.Output, input_c tf.Output, params 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 ...CudnnRNNBackpropV2Attr) (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: "CudnnRNNBackpropV2", - Input: []tf.Input{ - input, input_h, input_c, params, 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) -} - // StringFormatAttr is an optional argument to StringFormat. type StringFormatAttr func(optionalAttr) -- GitLab From 59f873cf45a10fcb029ae282448df921a71a4d4c Mon Sep 17 00:00:00 2001 From: Jiri Simsa Date: Tue, 16 Oct 2018 12:03:02 -0700 Subject: [PATCH 0153/1825] fixing minor documentation nits PiperOrigin-RevId: 217359845 --- tensorflow/contrib/distribute/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/distribute/README.md b/tensorflow/contrib/distribute/README.md index 2e025765e4..b416619fc1 100644 --- a/tensorflow/contrib/distribute/README.md +++ b/tensorflow/contrib/distribute/README.md @@ -20,7 +20,7 @@ on many GPUs on one machine. Essentially, we create copies of all variables in the model's layers on each device. We then use all-reduce to combine gradients across the devices before applying them to the variables to keep them in sync. * [`CollectiveAllReduceStrategy`](https://www.tensorflow.org/versions/master/api_docs/python/tf/contrib/distribute/CollectiveAllReduceStrategy): -This is a version of `MirroredStrategy` for multi-working training. It uses +This is a version of `MirroredStrategy` for multi-worker training. It uses a collective op to do all-reduce. This supports between-graph communication and synchronization, and delegates the specifics of the all-reduce implementation to the runtime (as opposed to encoding it in the graph). This allows it to perform @@ -31,8 +31,8 @@ fault-tolerance to allow training to continue when there is worker failure. * [`ParameterServerStrategy`](https://www.tensorflow.org/versions/master/api_docs/python/tf/contrib/distribute/ParameterServerStrategy): This strategy supports using parameter servers either for multi-GPU local training or asynchronous multi-machine training. When used to train locally, -variables are not mirrored, instead they placed on the CPU and operations are -replicated across all local GPUs. In a multi-machine setting, some are +variables are not mirrored, instead they are placed on the CPU and operations +are replicated across all local GPUs. In a multi-machine setting, some are designated as workers and some as parameter servers. Each variable is placed on one parameter server. Computation operations are replicated across all GPUs of the workers. -- GitLab From 6bfb36b241dadfecb345edb0589a8d0ae72dc968 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 12:06:21 -0700 Subject: [PATCH 0154/1825] Move from deprecated self.test_session() to self.session() or self.cached_session(). Move to cached_session() if the session is create more than once per test. Move to session() otherwise. self.test_session() has been deprecated in 9962eb5e84b15e309410071b06c2ed2d6148ed44 as its name confuses readers of the test. Moving to session() instead which slightly changes the semantic of the function: * the session is not cached anymore (a new session is created). * the session is closed when exiting the "with" scope. PiperOrigin-RevId: 217360604 --- .../layers/python/layers/layers_test.py | 36 +++++++++---------- .../python/kernel_tests/mel_ops_test.py | 4 +-- .../python/kernel_tests/mfcc_ops_test.py | 4 +-- .../kernel_tests/reconstruction_ops_test.py | 18 +++++----- .../python/kernel_tests/shape_ops_test.py | 24 ++++++------- .../python/kernel_tests/spectral_ops_test.py | 14 ++++---- .../python/kernel_tests/window_ops_test.py | 2 +- 7 files changed, 51 insertions(+), 51 deletions(-) diff --git a/tensorflow/contrib/layers/python/layers/layers_test.py b/tensorflow/contrib/layers/python/layers/layers_test.py index 3b7ae72e9c..8ead6336a0 100644 --- a/tensorflow/contrib/layers/python/layers/layers_test.py +++ b/tensorflow/contrib/layers/python/layers/layers_test.py @@ -630,7 +630,7 @@ class ConvolutionTest(test.TestCase): expected_size = [None, num_filters, None, None] expected_size_dynamic = [5, num_filters, 7, 9] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): images = array_ops.placeholder(np.float32, [None, input_size[1], None, None]) output = layers_lib.convolution2d( @@ -721,7 +721,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWithStrideOneSamePaddingNCHW(self): # `NCHW` data format is only supported for `GPU` device. if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 32 input_size = [5, 3, 10, 12] expected_size = [5, num_filters, 10, 12] @@ -740,7 +740,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWithStrideOneValidPaddingNCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 32 input_size = [5, 3, 10, 12] expected_size = [5, num_filters, 12, 14] @@ -759,7 +759,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWithStrideTwoValidPaddingNCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 32 input_size = [5, 3, 9, 11] expected_size = [5, num_filters, 19, 23] @@ -779,7 +779,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWith1x1StrideTwoSamePaddingNCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 1 input_size = [1, 1, 1, 1] expected_size = [1, num_filters, 2, 2] @@ -799,7 +799,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWith1x1StrideTwoValidPaddingNCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 1 input_size = [1, 1, 1, 1] expected_size = [1, num_filters, 2, 2] @@ -817,7 +817,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWith2x2StrideTwoSamePaddingNCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 1 input_size = [1, 1, 2, 2] expected_size = [1, num_filters, 4, 4] @@ -835,7 +835,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWith2x2StrideTwoValidPaddingNCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 1 input_size = [1, 1, 2, 2] expected_size = [1, num_filters, 4, 4] @@ -853,7 +853,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWithStride2x1NCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 1 input_size = [1, 1, 3, 2] expected_size = [1, num_filters, 6, 5] @@ -871,7 +871,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWithStride2x4NCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 1 input_size = [1, 1, 3, 2] expected_size = [1, num_filters, 6, 8] @@ -889,7 +889,7 @@ class Convolution2dTransposeTests(test.TestCase): def testOutputSizeWithStride2x5NCHW(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: num_filters = 1 input_size = [1, 1, 3, 2] expected_size = [1, num_filters, 6, 10] @@ -2056,7 +2056,7 @@ class BatchNormTest(test.TestCase): channels = 3 np.random.seed(1) use_gpu = fused - with self.test_session(use_gpu=use_gpu) as sess: + with self.session(use_gpu=use_gpu) as sess: if data_format == 'NHWC': image_shape = (batch_size, height, width, channels) axis = (0, 1, 2) @@ -2140,7 +2140,7 @@ class BatchNormTest(test.TestCase): channels = 3 np.random.seed(1) use_gpu = fused - with self.test_session(use_gpu=use_gpu) as sess: + with self.session(use_gpu=use_gpu) as sess: if data_format == 'NHWC': image_shape = (batch_size, height, width, channels) axis = (0, 1, 2) @@ -2344,7 +2344,7 @@ class BatchNormTest(test.TestCase): np.random.seed(1) use_gpu = fused np.random.seed(1) - with self.test_session(use_gpu=use_gpu) as sess: + with self.session(use_gpu=use_gpu) as sess: if data_format == 'NHWC': image_shape = (batch_size, height, width, channels) axis = (0, 1, 2) @@ -2491,7 +2491,7 @@ class BatchNormTest(test.TestCase): channels = 3 np.random.seed(1) use_gpu = fused - with self.test_session(use_gpu=use_gpu) as sess: + with self.session(use_gpu=use_gpu) as sess: if data_format == 'NHWC': image_shape = (batch_size, height, width, channels) axis = (0, 1, 2) @@ -2576,7 +2576,7 @@ class BatchNormTest(test.TestCase): channels = 32 np.random.seed(1) use_gpu = fused - with self.test_session(use_gpu=use_gpu) as sess: + with self.session(use_gpu=use_gpu) as sess: if data_format == 'NHWC': image_shape = (batch_size, height, width, channels) axis = (0, 1, 2) @@ -2674,7 +2674,7 @@ class BatchNormTest(test.TestCase): def _runBatchNormalizationWithFormat(self, shape, data_format, is_training): channels = shape[-1] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: images = np.arange(np.product(shape), dtype=np.float32).reshape(shape) beta = init_ops.constant_initializer( np.arange(2, channels + 2, dtype=np.float32)) @@ -2776,7 +2776,7 @@ class BatchNormTest(test.TestCase): 'moving_variance': variance, }, data_format='NCHW') - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run(variables_lib.global_variables_initializer()) return sess.run(output) diff --git a/tensorflow/contrib/signal/python/kernel_tests/mel_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/mel_ops_test.py index f4348e80ea..13ee8764b7 100644 --- a/tensorflow/contrib/signal/python/kernel_tests/mel_ops_test.py +++ b/tensorflow/contrib/signal/python/kernel_tests/mel_ops_test.py @@ -137,7 +137,7 @@ class LinearToMelTest(test.TestCase): # Settings used by Tacotron (https://arxiv.org/abs/1703.10135). (80, 1025, 24000.0, 80.0, 12000.0, dtypes.float64) ] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for config in configs: mel_matrix_np = spectrogram_to_mel_matrix(*config) mel_matrix = mel_ops.linear_to_mel_weight_matrix(*config) @@ -178,7 +178,7 @@ class LinearToMelTest(test.TestCase): self.assertEqual(1, len(rewritten_graph.node)) def test_num_spectrogram_bins_dynamic(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): num_spectrogram_bins = array_ops.placeholder(shape=(), dtype=dtypes.int32) mel_matrix_np = spectrogram_to_mel_matrix( diff --git a/tensorflow/contrib/signal/python/kernel_tests/mfcc_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/mfcc_ops_test.py index e7743bdcba..9de1e2c2f4 100644 --- a/tensorflow/contrib/signal/python/kernel_tests/mfcc_ops_test.py +++ b/tensorflow/contrib/signal/python/kernel_tests/mfcc_ops_test.py @@ -46,14 +46,14 @@ class MFCCTest(test.TestCase): def test_basic(self): """A basic test that the op runs on random input.""" with spectral_ops_test_util.fft_kernel_label_map(): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): signal = random_ops.random_normal((2, 3, 5)) mfcc_ops.mfccs_from_log_mel_spectrograms(signal).eval() def test_unknown_shape(self): """A test that the op runs when shape and rank are unknown.""" with spectral_ops_test_util.fft_kernel_label_map(): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): signal = array_ops.placeholder_with_default( random_ops.random_normal((2, 3, 5)), tensor_shape.TensorShape(None)) self.assertIsNone(signal.shape.ndims) diff --git a/tensorflow/contrib/signal/python/kernel_tests/reconstruction_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/reconstruction_ops_test.py index 5c9b2ac518..c476cd4e00 100644 --- a/tensorflow/contrib/signal/python/kernel_tests/reconstruction_ops_test.py +++ b/tensorflow/contrib/signal/python/kernel_tests/reconstruction_ops_test.py @@ -55,7 +55,7 @@ class ReconstructionOpsTest(test.TestCase): signal = constant_op.constant(np.ones((3, 5)), dtype=dtypes.int64) reconstruction = reconstruction_ops.overlap_and_add(signal, 2) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: output = sess.run(reconstruction) expected_output = np.array([1, 1, 2, 2, 3, 2, 2, 1, 1]) @@ -86,7 +86,7 @@ class ReconstructionOpsTest(test.TestCase): (make_input(4), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 4), ] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for signal, expected, frame_hop in configurations: reconstruction = reconstruction_ops.overlap_and_add( np.array(signal), frame_hop).eval() @@ -98,7 +98,7 @@ class ReconstructionOpsTest(test.TestCase): dtype=dtypes.int64) reconstruction = reconstruction_ops.overlap_and_add(signal, self.frame_hop) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: output = sess.run(reconstruction) string_output = [np.base_repr(x, self.bases[0]) for x in output] @@ -108,7 +108,7 @@ class ReconstructionOpsTest(test.TestCase): signal = constant_op.constant(self.powers, dtype=dtypes.int64) reconstruction = reconstruction_ops.overlap_and_add(signal, self.frame_hop) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: output = sess.run(reconstruction) accumulator = True @@ -124,7 +124,7 @@ class ReconstructionOpsTest(test.TestCase): signal = constant_op.constant(input_matrix, dtype=dtypes.float32) reconstruction = reconstruction_ops.overlap_and_add(signal, self.frame_hop) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: output = sess.run(reconstruction) string_output = [np.base_repr(int(x), self.bases[0]) for x in @@ -143,8 +143,8 @@ class ReconstructionOpsTest(test.TestCase): ((2, 2, 2, 10, 128), 125), ] - for shape, frame_hop in configurations: - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: + for shape, frame_hop in configurations: signal = array_ops.zeros(shape) reconstruction = reconstruction_ops.overlap_and_add(signal, frame_hop) loss = math_ops.reduce_sum(reconstruction) @@ -155,7 +155,7 @@ class ReconstructionOpsTest(test.TestCase): self.assertTrue((gradient == 1.0).all()) def test_gradient_batch(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: signal = array_ops.zeros((2, 10, 10)) frame_hop = 10 reconstruction = reconstruction_ops.overlap_and_add(signal, frame_hop) @@ -177,7 +177,7 @@ class ReconstructionOpsTest(test.TestCase): self.assertAllEqual(expected_gradient, gradient) def test_gradient_numerical(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = (2, 10, 10) framed_signal = array_ops.zeros(shape) frame_hop = 10 diff --git a/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py index f132050153..838025a040 100644 --- a/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py +++ b/tensorflow/contrib/signal/python/kernel_tests/shape_ops_test.py @@ -33,7 +33,7 @@ from tensorflow.python.platform import test class FrameTest(test.TestCase): def test_mapping_of_indices_without_padding(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tensor = constant_op.constant(np.arange(9152), dtypes.int32) tensor = array_ops.expand_dims(tensor, 0) @@ -48,7 +48,7 @@ class FrameTest(test.TestCase): self.assertAllEqual(expected, result) def test_mapping_of_indices_with_padding(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tensor = constant_op.constant(np.arange(10000), dtypes.int32) tensor = array_ops.expand_dims(tensor, 0) @@ -89,7 +89,7 @@ class FrameTest(test.TestCase): frame_length = 2 frame_step = 1 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): result = shape_ops.frame(signal, frame_length, frame_step, pad_end=True, pad_value=99).eval() self.assertEqual((0, 2), result.shape) @@ -149,7 +149,7 @@ class FrameTest(test.TestCase): for pad_end in [False, True]: op = shape_ops.frame(signal, frame_length, frame_step, pad_end=pad_end, pad_value=99) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): result = op.eval() self.assertEqual(op.shape.as_list(), list(result.shape)) @@ -158,7 +158,7 @@ class FrameTest(test.TestCase): frame_length = 3 frame_step = 2 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for rank in range(5): nd_signal = np.reshape(signal, (1,) * rank + signal.shape) @@ -184,7 +184,7 @@ class FrameTest(test.TestCase): frame_length = 3 frame_step = 2 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for rank in range(5): nd_signal = np.reshape(signal, (1,) * rank + signal.shape) @@ -218,7 +218,7 @@ class FrameTest(test.TestCase): frame_length = 3 frame_step = 2 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # With padding, we pad the last frame with pad_value. result = shape_ops.frame(signal, frame_length, frame_step, pad_end=True, pad_value=99).eval() @@ -244,7 +244,7 @@ class FrameTest(test.TestCase): def test_axis(self): signal = np.reshape(np.arange(16), (2, 4, 2)) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): result = shape_ops.frame(signal, frame_length=2, frame_step=2, pad_end=True, axis=1) expected = np.reshape(np.arange(16), (2, 2, 2, 2)) @@ -279,7 +279,7 @@ class FrameTest(test.TestCase): frame_length = 4 frame_step = 1 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): result = shape_ops.frame(signal, frame_length, frame_step, pad_end=True, pad_value=99).eval() self.assertAllClose([[[1, 2, 99, 99], [2, 99, 99, 99]], @@ -303,7 +303,7 @@ class FrameTest(test.TestCase): frame_length = 2 frame_step = 3 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): result = shape_ops.frame(signal, frame_length, frame_step) self.assertEqual(result.dtype, signal.dtype) @@ -315,7 +315,7 @@ class FrameTest(test.TestCase): frame_length = 2 frame_step = 2 - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: signal_placeholder = array_ops.placeholder(shape=(None, None), dtype=dtypes.float32) result = sess.run(shape_ops.frame( @@ -326,7 +326,7 @@ class FrameTest(test.TestCase): [[20, 21], [22, 23]]], result) def test_gradient_numerical(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): signal_shape = (2, 128) signal = array_ops.ones(signal_shape) frame_length = 33 diff --git a/tensorflow/contrib/signal/python/kernel_tests/spectral_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/spectral_ops_test.py index f10d78259a..5106a22f88 100644 --- a/tensorflow/contrib/signal/python/kernel_tests/spectral_ops_test.py +++ b/tensorflow/contrib/signal/python/kernel_tests/spectral_ops_test.py @@ -81,7 +81,7 @@ class SpectralOpsTest(test.TestCase): def _compare(self, signal, frame_length, frame_step, fft_length): with spectral_ops_test_util.fft_kernel_label_map(), ( - self.test_session(use_gpu=True)) as sess: + self.cached_session(use_gpu=True)) as sess: actual_stft = spectral_ops.stft( signal, frame_length, frame_step, fft_length, pad_end=False) signal_ph = array_ops.placeholder(dtype=dtypes.as_dtype(signal.dtype)) @@ -117,7 +117,7 @@ class SpectralOpsTest(test.TestCase): def test_shapes(self): with spectral_ops_test_util.fft_kernel_label_map(), ( - self.test_session(use_gpu=True)): + self.session(use_gpu=True)): signal = np.zeros((512,)).astype(np.float32) # If fft_length is not provided, the smallest enclosing power of 2 of @@ -188,7 +188,7 @@ class SpectralOpsTest(test.TestCase): signal = random_ops.random_normal([signal_length]) with spectral_ops_test_util.fft_kernel_label_map(), ( - self.test_session(use_gpu=True)) as sess: + self.cached_session(use_gpu=True)) as sess: stft = spectral_ops.stft(signal, frame_length, frame_step, fft_length, pad_end=False) inverse_stft = spectral_ops.inverse_stft(stft, frame_length, frame_step, @@ -234,7 +234,7 @@ class SpectralOpsTest(test.TestCase): inverse_window_fn = spectral_ops.inverse_stft_window_fn(frame_step) inverse_window = inverse_window_fn(frame_length, dtype=dtypes.float32) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: hann_window, inverse_window = sess.run([hann_window, inverse_window]) # Expect unit gain at each phase of the window. @@ -262,7 +262,7 @@ class SpectralOpsTest(test.TestCase): inverse_window_fn = spectral_ops.inverse_stft_window_fn(frame_step) inverse_window = inverse_window_fn(frame_length, dtype=dtypes.float32) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: hann_window, inverse_window = sess.run([hann_window, inverse_window]) self.assertAllClose(hann_window, inverse_window * 1.5) @@ -279,7 +279,7 @@ class SpectralOpsTest(test.TestCase): def test_gradients(self): """Test that spectral_ops.stft has a working gradient.""" with spectral_ops_test_util.fft_kernel_label_map(), ( - self.test_session(use_gpu=True)) as sess: + self.session(use_gpu=True)) as sess: signal_length = 512 # An all-zero signal has all zero gradients with respect to the sum of the @@ -298,7 +298,7 @@ class SpectralOpsTest(test.TestCase): def test_gradients_numerical(self): with spectral_ops_test_util.fft_kernel_label_map(), ( - self.test_session(use_gpu=True)): + self.session(use_gpu=True)): # Tuples of (signal_length, frame_length, frame_step, fft_length, # stft_bound, inverse_stft_bound). # TODO(rjryan): Investigate why STFT gradient error is so high. diff --git a/tensorflow/contrib/signal/python/kernel_tests/window_ops_test.py b/tensorflow/contrib/signal/python/kernel_tests/window_ops_test.py index 5a464699da..6a46a22693 100644 --- a/tensorflow/contrib/signal/python/kernel_tests/window_ops_test.py +++ b/tensorflow/contrib/signal/python/kernel_tests/window_ops_test.py @@ -64,7 +64,7 @@ class WindowOpsTest(test.TestCase): (dtypes.float64, 1e-9)] def _compare_window_fns(self, np_window_fn, tf_window_fn): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for window_length in self._window_lengths: for periodic in [False, True]: for tf_dtype, tol in self._dtypes: -- GitLab From 1d02bb4cbaa9308dd7b4ad21ff1c74dd4134f920 Mon Sep 17 00:00:00 2001 From: Sergei Lebedev Date: Tue, 16 Oct 2018 21:25:39 +0200 Subject: [PATCH 0155/1825] Fixed indentation in test_ps_session_config --- tensorflow/python/estimator/run_config_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/estimator/run_config_test.py b/tensorflow/python/estimator/run_config_test.py index 313bf62c05..e886ac7344 100644 --- a/tensorflow/python/estimator/run_config_test.py +++ b/tensorflow/python/estimator/run_config_test.py @@ -1197,8 +1197,8 @@ class RunConfigSessionConfigTest(test.TestCase): } run_config = _create_run_config_with_cluster_spec(tf_config) self._assert_equal_session_config( - run_config.session_config, - ['/job:ps', '/job:worker', '/job:chief', '/job:master']) + run_config.session_config, + ['/job:ps', '/job:worker', '/job:chief', '/job:master']) def test_evaluator_session_config(self): tf_config = { -- GitLab From a1717b77f5a4fa2b5869adb660be3a74c6b02618 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 12:27:38 -0700 Subject: [PATCH 0156/1825] [tfgan] Exposed warm start functionality to GANEstimator. PiperOrigin-RevId: 217363868 --- .../estimator/python/gan_estimator_impl.py | 8 ++- .../estimator/python/gan_estimator_test.py | 68 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/gan/python/estimator/python/gan_estimator_impl.py b/tensorflow/contrib/gan/python/estimator/python/gan_estimator_impl.py index 7243f150ce..219cc199d7 100644 --- a/tensorflow/contrib/gan/python/estimator/python/gan_estimator_impl.py +++ b/tensorflow/contrib/gan/python/estimator/python/gan_estimator_impl.py @@ -112,7 +112,8 @@ class GANEstimator(estimator.Estimator): get_eval_metric_ops_fn=None, add_summaries=None, use_loss_summaries=True, - config=None): + config=None, + warm_start_from=None): """Initializes a GANEstimator instance. Args: @@ -151,6 +152,8 @@ class GANEstimator(estimator.Estimator): use_loss_summaries: If `True`, add loss summaries. If `False`, does not. If `None`, uses defaults. config: `RunConfig` object to configure the runtime settings. + warm_start_from: A filepath to a checkpoint or saved model, or a + WarmStartSettings object to configure initialization. Raises: ValueError: If loss functions aren't callable. @@ -187,7 +190,8 @@ class GANEstimator(estimator.Estimator): get_hooks_fn, use_loss_summaries) super(GANEstimator, self).__init__( - model_fn=_model_fn, model_dir=model_dir, config=config) + model_fn=_model_fn, model_dir=model_dir, config=config, + warm_start_from=warm_start_from) def _get_gan_model( diff --git a/tensorflow/contrib/gan/python/estimator/python/gan_estimator_test.py b/tensorflow/contrib/gan/python/estimator/python/gan_estimator_test.py index 83f8dd641f..cfc867f083 100644 --- a/tensorflow/contrib/gan/python/estimator/python/gan_estimator_test.py +++ b/tensorflow/contrib/gan/python/estimator/python/gan_estimator_test.py @@ -33,9 +33,11 @@ from tensorflow.contrib.learn.python.learn.learn_io import graph_io from tensorflow.core.example import example_pb2 from tensorflow.core.example import feature_pb2 from tensorflow.python.estimator import model_fn as model_fn_lib +from tensorflow.python.estimator.estimator import WarmStartSettings from tensorflow.python.estimator.inputs import numpy_io from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops +from tensorflow.python.framework.errors_impl import NotFoundError from tensorflow.python.ops import array_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import metrics as metrics_lib @@ -317,5 +319,71 @@ class GANEstimatorIntegrationTest(test.TestCase): prediction_size=[batch_size, input_dim]) +class GANEstimatorWarmStartTest(test.TestCase): + + def setUp(self): + self._model_dir = self.get_temp_dir() + self.new_variable_name = 'new_var' + self.new_variable_value = [1, 2, 3] + + def tearDown(self): + writer_cache.FileWriterCache.clear() + + def _test_warm_start(self, warm_start_from=None): + """Tests whether WarmStartSettings work as intended.""" + def generator_with_new_variable(noise_dict, mode): + variable_scope.get_variable(name=self.new_variable_name, + initializer=self.new_variable_value, + trainable=True) + return generator_fn(noise_dict, mode) + + def train_input_fn(): + data = np.zeros([3, 4]) + return {'x': data}, data + + est = estimator.GANEstimator( + generator_fn=generator_fn, + discriminator_fn=discriminator_fn, + generator_loss_fn=losses.wasserstein_generator_loss, + discriminator_loss_fn=losses.wasserstein_discriminator_loss, + generator_optimizer=training.GradientDescentOptimizer(1.0), + discriminator_optimizer=training.GradientDescentOptimizer(1.0), + model_dir=self._model_dir) + + est.train(train_input_fn, steps=1) + + est_warm = estimator.GANEstimator( + generator_fn=generator_with_new_variable, + discriminator_fn=discriminator_fn, + generator_loss_fn=losses.wasserstein_generator_loss, + discriminator_loss_fn=losses.wasserstein_discriminator_loss, + generator_optimizer=training.GradientDescentOptimizer(1.0), + discriminator_optimizer=training.GradientDescentOptimizer(1.0), + model_dir=None if warm_start_from else self._model_dir, + warm_start_from=warm_start_from) + + est_warm.train(train_input_fn, steps=1) + + return est_warm + + def test_warm_start_error(self): + """Test if exception when reloading different estimators.""" + with self.assertRaises(NotFoundError): + self._test_warm_start() + + def test_warm_start_success(self): + """Test if GANEstimator allows explicit warm start variable assignment.""" + # Regex matches all variable names in ckpt except for new_var. + var_regex = '^(?!.*%s.*)' % self.new_variable_name + warmstart = WarmStartSettings(ckpt_to_initialize_from=self._model_dir, + vars_to_warm_start=var_regex) + est_warm = self._test_warm_start(warm_start_from=warmstart) + full_variable_name = 'Generator/%s' % self.new_variable_name + self.assertIn(full_variable_name, est_warm.get_variable_names()) + equal_vals = np.array_equal(est_warm.get_variable_value(full_variable_name), + self.new_variable_value) + self.assertTrue(equal_vals) + + if __name__ == '__main__': test.main() -- GitLab From 52589599b3fe467225b174a28271d52d50c4d54c Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 13:04:06 -0700 Subject: [PATCH 0157/1825] Re-enable tests for tf.einsum(). These tests check that tf.einsum() is equivalent to np.einsum(). At one point, some of them were failing because of a bug in np.einsum() in an old version of numpy. This bug has been fixed, so we can re-enable the tests now. PiperOrigin-RevId: 217370167 --- tensorflow/python/ops/special_math_ops_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/python/ops/special_math_ops_test.py b/tensorflow/python/ops/special_math_ops_test.py index b9dfc79311..d2f6b47697 100644 --- a/tensorflow/python/ops/special_math_ops_test.py +++ b/tensorflow/python/ops/special_math_ops_test.py @@ -240,7 +240,7 @@ class EinsumTest(test.TestCase): 'aef,fbc,dca->bde', 'iJ,Jk->ik', 'iJ,Ki->JK', - 'iJk,Jklm->Jk' + 'iJk,Jklm->Jk', 'ij, jk, kl -> il', 'a, ab, abc -> abc', 'ab, ab, cd, cd, ef, ef -> ', @@ -280,7 +280,7 @@ class EinsumTest(test.TestCase): dim_mismatch_cases = [('ijk,jkl->il', [(2, 3, 4), (3, 5, 6)])] - def disabled_test_simple(self): + def test_simple(self): for case in self.simple_cases: self.run_test(case) -- GitLab From 4b29bd950b6763a1d2ade08369ed1e432d492af4 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Tue, 16 Oct 2018 13:05:41 -0700 Subject: [PATCH 0158/1825] Adding an XLA kernel registration for kDeviceRetOp that returns int32 tensors in device memory rather than host memory. PiperOrigin-RevId: 217370484 --- tensorflow/compiler/jit/xla_device_ops.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/compiler/jit/xla_device_ops.h b/tensorflow/compiler/jit/xla_device_ops.h index 14a232b7a8..6a1c43aa96 100644 --- a/tensorflow/compiler/jit/xla_device_ops.h +++ b/tensorflow/compiler/jit/xla_device_ops.h @@ -210,6 +210,8 @@ class XlaAssignVariableOp : public AsyncOpKernel { .TypeConstraint("T") \ .HostMemory("input"), \ RetvalOp); \ + REGISTER_KERNEL_BUILDER( \ + Name(kDeviceRetOp).Device(DEVICE).TypeConstraint("T"), RetvalOp); \ \ REGISTER_KERNEL_BUILDER( \ Name("RemoteCall").Device(DEVICE).HostMemory("target"), RemoteCallOp); \ -- GitLab From 0326792aa76a132ab0c9bb8cc8dcb6ce0b1487fb Mon Sep 17 00:00:00 2001 From: mdfaijul Date: Tue, 16 Oct 2018 13:21:58 -0700 Subject: [PATCH 0159/1825] remove CHECK and CHECK_EQ, added TF_RETURN_IF_ERROR --- .../graph_transforms/fuse_quantized_convolution.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tensorflow/tools/graph_transforms/fuse_quantized_convolution.cc b/tensorflow/tools/graph_transforms/fuse_quantized_convolution.cc index 2128bcd978..bd021d094e 100644 --- a/tensorflow/tools/graph_transforms/fuse_quantized_convolution.cc +++ b/tensorflow/tools/graph_transforms/fuse_quantized_convolution.cc @@ -179,8 +179,14 @@ Status FuseQuantizedConvolutionAndRequantize( TensorProto float_tensor_proto = bias_node->attr().at("value").tensor(); Tensor float_tensor; - CHECK(float_tensor.FromProto(float_tensor_proto)); - CHECK_EQ(float_tensor.dtype(), DT_FLOAT); + if(!float_tensor.FromProto(float_tensor_proto)) { + TF_RETURN_IF_ERROR(::tensorflow::errors::InvalidArgument( + "TensorProto object is not valid.")); + } + if (float_tensor.dtype() != DT_FLOAT) { + TF_RETURN_IF_ERROR(::tensorflow::errors::Unimplemented( + "Expected float tensor.")); + } float *p_bias_float = float_tensor.flat().data(); Tensor int32_tensor = Tensor(DT_QINT32, float_tensor.shape()); -- GitLab From 6e23a13b1ec2da3d1327570a9661c9663fab82a0 Mon Sep 17 00:00:00 2001 From: Ruoxin Sang Date: Tue, 16 Oct 2018 13:27:12 -0700 Subject: [PATCH 0160/1825] Fix a bug in TPUEstimator that eval_steps is not increased correctly when no eval_metrics is passed. PiperOrigin-RevId: 217374906 --- .../contrib/tpu/python/tpu/tpu_estimator.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py index b2fa9eb45c..29aa0d6568 100644 --- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py +++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py @@ -2609,10 +2609,6 @@ class TPUEstimator(estimator_lib.Estimator): total_loss, math_ops.cast(iterations_per_loop_var, dtype=total_loss.dtype)) - # Creates a dummy metric update_op for all metrics. Estimator expects - # all metrics in eval_metric_ops have update_op and calls them one by - # one. The real metric update_ops are invoked in a separated thread. - # So, here give Estimator the dummy op for all metrics. with ops.control_dependencies([mean_loss]): # After TPU evaluation computation is done (the mean_loss tensor), # reads all variables back from TPU and updates the eval step @@ -2620,16 +2616,30 @@ class TPUEstimator(estimator_lib.Estimator): internal_ops_to_run = _sync_variables_ops(ctx) internal_ops_to_run.append( _increase_eval_step_op(iterations_per_loop_var)) - with ops.control_dependencies(internal_ops_to_run): - dummy_update_op = control_flow_ops.no_op() host_call_ret = host_calls.create_tpu_hostcall() eval_metric_ops = {} eval_update_ops = [] - for k, v in host_call_ret.get('eval_metrics', {}).items(): - eval_metric_ops[k] = (v[0], dummy_update_op) - eval_update_ops.append(v[1]) + eval_metrics = host_call_ret.get('eval_metrics', {}) + if eval_metrics: + # Creates a dummy metric update_op for all metrics. Estimator + # expects all metrics in `eval_metric_ops` have update_op and calls + # them one by one. The real metric update_ops are invoked in a + # separated thread. So, here give Estimator the dummy op for all + # metrics. + with ops.control_dependencies(internal_ops_to_run): + dummy_update_op = control_flow_ops.no_op() + + for k, v in eval_metrics.items(): + eval_metric_ops[k] = (v[0], dummy_update_op) + eval_update_ops.append(v[1]) + else: + # If no eval metrics are passed, create an identity node for the + # loss and add `internal_ops_to_run` to its dependencies. So + # `internal_ops_to_run` can be executed. + with ops.control_dependencies(internal_ops_to_run): + mean_loss = array_ops.identity(mean_loss) if 'host_call' not in host_call_ret: host_ops = [] -- GitLab From f2d88e5ad422bb4abc2db1ddbac0f9247fd95896 Mon Sep 17 00:00:00 2001 From: Mihai Maruseac Date: Tue, 16 Oct 2018 13:29:00 -0700 Subject: [PATCH 0161/1825] Modify variable_scope_test.py to work equally well under graph and eager execution. PiperOrigin-RevId: 217375275 --- tensorflow/python/eager/BUILD | 1 + tensorflow/python/kernel_tests/BUILD | 1 + .../kernel_tests/variable_scope_test.py | 1447 ++++++++++------- 3 files changed, 896 insertions(+), 553 deletions(-) diff --git a/tensorflow/python/eager/BUILD b/tensorflow/python/eager/BUILD index 52ea495305..751e8c402e 100644 --- a/tensorflow/python/eager/BUILD +++ b/tensorflow/python/eager/BUILD @@ -421,6 +421,7 @@ py_library( name = "wrap_function", srcs = ["wrap_function.py"], srcs_version = "PY2AND3", + visibility = ["//tensorflow:internal"], deps = [ ":context", ":function", diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 33fb925f09..3d9b886ebb 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -1172,6 +1172,7 @@ tf_py_test( "//tensorflow/python:variables", "//tensorflow/python/eager:context", "//tensorflow/python/eager:function", + "//tensorflow/python/eager:wrap_function", ], tags = ["no_windows"], ) diff --git a/tensorflow/python/kernel_tests/variable_scope_test.py b/tensorflow/python/kernel_tests/variable_scope_test.py index 33f464fb90..054e514a84 100644 --- a/tensorflow/python/kernel_tests/variable_scope_test.py +++ b/tensorflow/python/kernel_tests/variable_scope_test.py @@ -25,6 +25,7 @@ import numpy from tensorflow.python.eager import context from tensorflow.python.eager import function +from tensorflow.python.eager import wrap_function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors @@ -44,6 +45,25 @@ from tensorflow.python.util import compat from tensorflow.python.util import tf_inspect +def wrap_and_execute(graph_function, skip_graph=False): + """Wrap a graph _nullary_ function and execute it in graph and eager modes. + + If graph mode fails, use skip_graph=True and comment on the caller with the + failure reason. + + Args: + graph_function: python function containing graph code to be wrapped + skip_graph: Optional. Don't call the graph function if it errors. + """ + wrapped = wrap_function.wrap_function(graph_function, []) + if context.executing_eagerly(): + # use the wrapped graph function + wrapped() + elif not skip_graph: + # use the original function + graph_function() + + class VariableScopeTest(test.TestCase): def tearDown(self): @@ -52,18 +72,29 @@ class VariableScopeTest(test.TestCase): # involving objects with __del__ defined. self.assertEqual(0, len(gc.garbage)) + @test_util.run_in_graph_and_eager_modes def testGetVar(self): - vs = variable_scope._get_default_variable_store() - v = vs.get_variable("v", [1]) - v1 = vs.get_variable("v", [1]) - self.assertEqual(v, v1) + + def _f(): + vs = variable_scope._get_default_variable_store() + v = vs.get_variable("v", [1]) + v1 = vs.get_variable("v", [1]) + self.assertEqual(v, v1) + + wrap_and_execute(_f) @test_util.run_in_graph_and_eager_modes def testResource(self): - vs = variable_scope._get_default_variable_store() - v1 = vs.get_variable("v", [1], use_resource=True) - self.assertTrue(isinstance(v1, resource_variable_ops.ResourceVariable)) + def _f(): + vs = variable_scope._get_default_variable_store() + v1 = vs.get_variable("v", [1], use_resource=True) + self.assertTrue(isinstance(v1, resource_variable_ops.ResourceVariable)) + + wrap_and_execute(_f) + + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # AttributeError: Tensor.op is meaningless when eager execution is enabled. def testNameExists(self): vs = variable_scope._get_default_variable_store() # No check by default, so we can both create and get existing names. @@ -80,14 +111,22 @@ class VariableScopeTest(test.TestCase): with self.assertRaises(ValueError): vs.get_variable("u", [1], reuse=True) # That fails. + @test_util.run_in_graph_and_eager_modes def testNamelessStore(self): - vs = variable_scope._get_default_variable_store() - vs.get_variable("v1", [2]) - vs.get_variable("v2", [2]) - expected_names = ["%s:0" % name for name in ["v1", "v2"]] - self.assertEqual( - set(expected_names), set([v.name for v in vs._vars.values()])) + def _f(): + vs = variable_scope._get_default_variable_store() + vs.get_variable("v1", [2]) + vs.get_variable("v2", [2]) + expected_names = ["%s:0" % name for name in ["v1", "v2"]] + self.assertEqual( + set(expected_names), set([v.name for v in vs._vars.values()])) + + wrap_and_execute(_f) + + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Operation name: "tower0/foo/v/Assign" ... is not an element of + # this graph. @test_util.run_in_graph_and_eager_modes def testVarScopeInitializer(self): init = init_ops.constant_initializer(0.3) @@ -101,6 +140,8 @@ class VariableScopeTest(test.TestCase): self.evaluate(variables_lib.variables_initializer([w])) self.assertAllClose(self.evaluate(w.value()), 0.3) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Variable tower1/foo/v already exists, disallowed. @test_util.run_in_graph_and_eager_modes def testVarScopeConstraint(self): constraint = lambda x: 0. * x @@ -112,12 +153,18 @@ class VariableScopeTest(test.TestCase): w = variable_scope.get_variable("w", []) self.assertEqual(w.constraint, constraint) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # TypeError: Fetch argument + # has invalid type , must be a string or Tensor. + # (Can not convert a ResourceVariable into a Tensor or Operation.) def testStringDefaultInitializer(self): with self.cached_session(): v = variable_scope.get_variable("string", shape=[], dtype=dtypes.string) variables_lib.global_variables_initializer().run() - self.assertAllEqual(compat.as_bytes(v.eval()), b"") + self.assertAllEqual(compat.as_bytes(self.evaluate(v)), b"") + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Variable tower2/foo/v already exists, disallowed. @test_util.run_in_graph_and_eager_modes def testVarScopeDType(self): with variable_scope.variable_scope("tower2") as tower: @@ -198,6 +245,8 @@ class VariableScopeTest(test.TestCase): self.assertAllEqual([v1, v2], [v3, v4]) f() + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one @test_util.run_in_graph_and_eager_modes def testEagerVariablesStoreAddsToCollections(self): store = variable_scope.EagerVariableStore() @@ -215,15 +264,15 @@ class VariableScopeTest(test.TestCase): self.assertEqual( ops.get_collection(ops.GraphKeys.CONCATENATED_VARIABLES), [concat]) - @test_util.run_in_graph_and_eager_modes def testEagerVariablesOutsideStoreNotAddedToCollections(self): - if not context.executing_eagerly(): - return - variable_scope.get_variable("v1", [], trainable=True) - variable_scope.get_variable("v2", [], trainable=False) - self.assertFalse(ops.get_collection(ops.GraphKeys.GLOBAL_VARIABLES)) - self.assertFalse(ops.get_collection(ops.GraphKeys.TRAINABLE_VARIABLES)) + with context.eager_mode(): + variable_scope.get_variable("v1", [], trainable=True) + variable_scope.get_variable("v2", [], trainable=False) + self.assertFalse(ops.get_collection(ops.GraphKeys.GLOBAL_VARIABLES)) + self.assertFalse(ops.get_collection(ops.GraphKeys.TRAINABLE_VARIABLES)) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Operation name: "v4/Assign" ... is not an element of this graph. @test_util.run_in_graph_and_eager_modes def testInitFromNonTensorValue(self): v = variable_scope.get_variable("v4", initializer=4, dtype=dtypes.int32) @@ -240,6 +289,8 @@ class VariableScopeTest(test.TestCase): with self.assertRaises(error): variable_scope.get_variable("x4", initializer={}) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Operation name: "xx0/Assign" ...is not an element of this graph. @test_util.run_in_graph_and_eager_modes def testInitFromNonInitializer(self): # Test various dtypes with zeros initializer as following: @@ -262,6 +313,8 @@ class VariableScopeTest(test.TestCase): self.assertAllEqual(self.evaluate(x.value()), self.evaluate(y.value())) # TODO(alive): support variable partitioning/caching in eager mode. + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # InvalidArgumentError: /job:moo/replica:0/task:0/device:CPU:0 unknown device. def testVarScopeCachingDevice(self): with self.cached_session(): caching_device = "/job:moo" @@ -295,6 +348,8 @@ class VariableScopeTest(test.TestCase): v_tower = variable_scope.get_variable("v", []) self.assertFalse(v_tower.value().device.startswith(caching_device)) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Operation name: ".../Assign"... is not an element of this graph. @test_util.run_in_graph_and_eager_modes def testVarScopeRegularizer(self): init = init_ops.constant_initializer(0.3) @@ -340,6 +395,9 @@ class VariableScopeTest(test.TestCase): losses = ops.get_collection(ops.GraphKeys.REGULARIZATION_LOSSES) self.assertEqual(3, len(losses)) # No new loss added. + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Tensor-typed variable initializers must either be wrapped in an + # init_scope or callable... @test_util.run_in_graph_and_eager_modes def testInitializeFromValue(self): init = constant_op.constant(0.1) @@ -366,6 +424,11 @@ class VariableScopeTest(test.TestCase): with self.assertRaisesRegexp(ValueError, "don't match"): variable_scope.get_variable("s", initializer=init, dtype=dtypes.float64) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # TypeError: Fetch argument has + # invalid type , must + # be a string or Tensor. (Can not convert a ResourceVariable into a Tensor or + # Operation.) def testControlDeps(self): with self.cached_session() as sess: v0 = variable_scope.get_variable( @@ -390,6 +453,8 @@ class VariableScopeTest(test.TestCase): sess.run(v0.initializer) sess.run(add) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # AssertionError: True is not false (last assertFalse) def testEnableResourceVariables(self): old = variable_scope._DEFAULT_USE_RESOURCE try: @@ -402,6 +467,8 @@ class VariableScopeTest(test.TestCase): finally: variable_scope._DEFAULT_USE_RESOURCE = old + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # TypeError: Fetch argument None has invalid type def testControlFlow(self): with self.cached_session() as sess: v0 = variable_scope.get_variable( @@ -441,6 +508,8 @@ class VariableScopeTest(test.TestCase): sess.run(v0.initializer) sess.run(add) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Operation name: ".../Assign"... is not an element of this graph. @test_util.run_in_graph_and_eager_modes def testGetVariableScope(self): # Test the get_variable_scope() function and setting properties of result. @@ -464,123 +533,150 @@ class VariableScopeTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testVarScope(self): - with variable_scope.variable_scope("tower4") as tower: - self.assertEqual(tower.name, "tower4") - with ops.name_scope("scope") as sc: - self.assertEqual(sc, "tower4/scope/") - - with variable_scope.variable_scope("tower5"): - with variable_scope.variable_scope("bar") as bar: - self.assertEqual(bar.name, "tower5/bar") - with ops.name_scope("scope") as sc: - self.assertEqual(sc, "tower5/bar/scope/") - with variable_scope.variable_scope("tower6"): - with variable_scope.variable_scope(tower, reuse=True) as tower_shared: - self.assertEqual(tower_shared.name, "tower4") + def _f(): + with variable_scope.variable_scope("tower4") as tower: + self.assertEqual(tower.name, "tower4") with ops.name_scope("scope") as sc: - self.assertEqual(sc, "tower6/tower4/scope/") + self.assertEqual(sc, "tower4/scope/") + + with variable_scope.variable_scope("tower5"): + with variable_scope.variable_scope("bar") as bar: + self.assertEqual(bar.name, "tower5/bar") + with ops.name_scope("scope") as sc: + self.assertEqual(sc, "tower5/bar/scope/") + + with variable_scope.variable_scope("tower6"): + with variable_scope.variable_scope(tower, reuse=True) as tower_shared: + self.assertEqual(tower_shared.name, "tower4") + with ops.name_scope("scope") as sc: + self.assertEqual(sc, "tower6/tower4/scope/") + + wrap_and_execute(_f) @test_util.run_in_graph_and_eager_modes def testVarScopeNameScope(self): - with ops.name_scope("testVarScopeNameScope1"): - with variable_scope.variable_scope("tower") as tower: - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "testVarScopeNameScope1/tower/scope2/") - if not context.executing_eagerly(): - with variable_scope.variable_scope( - tower): # Re-entering acts like another "tower". + + def _f(): + with ops.name_scope("testVarScopeNameScope1"): + with variable_scope.variable_scope("tower") as tower: with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "testVarScopeNameScope1/tower_1/scope2/") - with variable_scope.variable_scope( - "tower"): # Re-entering by string acts the same. + self.assertEqual(sc2, "testVarScopeNameScope1/tower/scope2/") + if not context.executing_eagerly(): + with variable_scope.variable_scope( + tower): # Re-entering acts like another "tower". + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "testVarScopeNameScope1/tower_1/scope2/") + with variable_scope.variable_scope( + "tower"): # Re-entering by string acts the same. + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "testVarScopeNameScope1/tower_2/scope2/") + + with ops.name_scope("testVarScopeNameScope2"): + with variable_scope.variable_scope("tower"): with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "testVarScopeNameScope1/tower_2/scope2/") + self.assertEqual(sc2, "testVarScopeNameScope2/tower/scope2/") + if not context.executing_eagerly(): + with variable_scope.variable_scope(tower): + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "testVarScopeNameScope2/tower_1/scope2/") - with ops.name_scope("testVarScopeNameScope2"): - with variable_scope.variable_scope("tower"): - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "testVarScopeNameScope2/tower/scope2/") - if not context.executing_eagerly(): - with variable_scope.variable_scope(tower): + root_var_scope = variable_scope.get_variable_scope() + with ops.name_scope("testVarScopeNameScope3"): + with variable_scope.variable_scope(root_var_scope): with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "testVarScopeNameScope2/tower_1/scope2/") + self.assertEqual(sc2, "testVarScopeNameScope3/scope2/") - root_var_scope = variable_scope.get_variable_scope() - with ops.name_scope("testVarScopeNameScope3"): - with variable_scope.variable_scope(root_var_scope): - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "testVarScopeNameScope3/scope2/") + wrap_and_execute(_f) + @test_util.run_in_graph_and_eager_modes def testVarScopeOriginalNameScope(self): - with self.cached_session(): - with ops.name_scope("scope1"): - with variable_scope.variable_scope("tower") as tower: - self.assertEqual(tower.original_name_scope, "scope1/tower/") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "scope1/tower/scope2/") - with ops.name_scope("scope2"): - with variable_scope.variable_scope(tower) as tower1: - # Re-entering preserves original name scope. - self.assertEqual(tower1.original_name_scope, "scope1/tower/") - with ops.name_scope("foo") as sc2: - self.assertEqual(sc2, "scope2/tower/foo/") - # Test re-entering original name scope. - with ops.name_scope(tower.original_name_scope): - with ops.name_scope("bar") as sc3: - self.assertEqual(sc3, "scope1/tower/bar/") - with ops.name_scope("scope2"): - with variable_scope.variable_scope(tower): + + def _f(): + with self.cached_session(): + with ops.name_scope("scope1"): + with variable_scope.variable_scope("tower") as tower: + self.assertEqual(tower.original_name_scope, "scope1/tower/") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "scope1/tower/scope2/") + with ops.name_scope("scope2"): + with variable_scope.variable_scope(tower) as tower1: + # Re-entering preserves original name scope. + self.assertEqual(tower1.original_name_scope, "scope1/tower/") + with ops.name_scope("foo") as sc2: + self.assertEqual(sc2, "scope2/tower/foo/") + # Test re-entering original name scope. with ops.name_scope(tower.original_name_scope): with ops.name_scope("bar") as sc3: - self.assertEqual(sc3, "scope1/tower/bar_1/") + self.assertEqual(sc3, "scope1/tower/bar/") + with ops.name_scope("scope2"): + with variable_scope.variable_scope(tower): + with ops.name_scope(tower.original_name_scope): + with ops.name_scope("bar") as sc3: + self.assertEqual(sc3, "scope1/tower/bar_1/") + # TODO(mihaimaruseac): calling _f fails with + # AssertionError: 'scope1_1/tower/' != 'scope1/tower/' + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testVarScopeObjectReuse(self): - with self.cached_session(): - vs = None - with variable_scope.variable_scope("jump", reuse=True) as scope: - vs = scope - with variable_scope.variable_scope(vs) as jump: - self.assertTrue(jump.reuse) + def _f(): + with self.cached_session(): + vs = None + with variable_scope.variable_scope("jump", reuse=True) as scope: + vs = scope - with variable_scope.variable_scope(vs, reuse=True) as jump_reuse: - self.assertTrue(jump_reuse.reuse) + with variable_scope.variable_scope(vs) as jump: + self.assertTrue(jump.reuse) - with variable_scope.variable_scope(vs, reuse=False) as jump_no_reuse: - self.assertTrue(jump_no_reuse.reuse) # Inherited, cannot be undone. + with variable_scope.variable_scope(vs, reuse=True) as jump_reuse: + self.assertTrue(jump_reuse.reuse) - with variable_scope.variable_scope("jump", reuse=False) as scope: - vs = scope + with variable_scope.variable_scope(vs, reuse=False) as jump_no_reuse: + self.assertTrue(jump_no_reuse.reuse) # Inherited, cannot be undone. - with variable_scope.variable_scope(vs) as jump: - self.assertFalse(jump.reuse) + with variable_scope.variable_scope("jump", reuse=False) as scope: + vs = scope - with variable_scope.variable_scope(vs, reuse=True) as jump_reuse: - self.assertTrue(jump_reuse.reuse) + with variable_scope.variable_scope(vs) as jump: + self.assertFalse(jump.reuse) - with variable_scope.variable_scope(vs, reuse=False) as jump_no_reuse: - self.assertFalse(jump_no_reuse.reuse) + with variable_scope.variable_scope(vs, reuse=True) as jump_reuse: + self.assertTrue(jump_reuse.reuse) + with variable_scope.variable_scope(vs, reuse=False) as jump_no_reuse: + self.assertFalse(jump_no_reuse.reuse) + + wrap_and_execute(_f) + + @test_util.run_in_graph_and_eager_modes def testVarScopeGetOrCreateReuse(self): - with self.cached_session(): - def test_value(value): - x = constant_op.constant(value) - with variable_scope.variable_scope( - "testVarScopeGetOrCreateReuse_bar", - reuse=variable_scope.AUTO_REUSE): - _ = state_ops.assign(variable_scope.get_variable("var", []), x) - with variable_scope.variable_scope( - "testVarScopeGetOrCreateReuse_bar", - reuse=variable_scope.AUTO_REUSE): - _ = variable_scope.get_variable("var", []) - self.assertEqual(value, x.eval()) + def _f(): + with self.cached_session(): + + def test_value(value): + x = constant_op.constant(value) + with variable_scope.variable_scope( + "testVarScopeGetOrCreateReuse_bar", + reuse=variable_scope.AUTO_REUSE): + _ = state_ops.assign(variable_scope.get_variable("var", []), x) + with variable_scope.variable_scope( + "testVarScopeGetOrCreateReuse_bar", + reuse=variable_scope.AUTO_REUSE): + _ = variable_scope.get_variable("var", []) + self.assertEqual(value, x.eval()) + + test_value(42.) # Variable is created. + test_value(13.) # Variable is reused hereafter. + test_value(17.) - test_value(42.) # Variable is created. - test_value(13.) # Variable is reused hereafter. - test_value(17.) + wrap_and_execute(_f) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # AttributeError: Tensor.op is meaningless when eager execution is enabled. def testVarOpScope(self): with self.cached_session(): with ops.name_scope("testVarOpScope1"): @@ -607,71 +703,96 @@ class VariableScopeTest(test.TestCase): with ops.name_scope("testVarOpScope2") as sc2: self.assertEqual(sc2, "testVarOpScope2/default_1/testVarOpScope2/") + @test_util.run_in_graph_and_eager_modes def testVarOpScopeUniqueNamesInterleavedSubstringScopes(self): - with self.cached_session(): - with variable_scope.variable_scope(None, "defaultScope1"): - with variable_scope.variable_scope(None, "layer"): - self.assertEqual( - variable_scope.get_variable("w", []).name, - "defaultScope1/layer/w:0") - with variable_scope.variable_scope(None, "defaultScope1"): - with variable_scope.variable_scope(None, "layer"): - self.assertEqual( - variable_scope.get_variable("w", []).name, - "defaultScope1_1/layer/w:0") - with variable_scope.variable_scope(None, "defaultScope"): - with variable_scope.variable_scope(None, "layer"): - self.assertEqual( - variable_scope.get_variable("w", []).name, - "defaultScope/layer/w:0") - with variable_scope.variable_scope(None, "defaultScope1"): - with variable_scope.variable_scope(None, "layer"): - self.assertEqual( - variable_scope.get_variable("w", []).name, - "defaultScope1_2/layer/w:0") + def _f(): + with self.cached_session(): + with variable_scope.variable_scope(None, "defaultScope1"): + with variable_scope.variable_scope(None, "layer"): + self.assertEqual( + variable_scope.get_variable("w", []).name, + "defaultScope1/layer/w:0") + with variable_scope.variable_scope(None, "defaultScope1"): + with variable_scope.variable_scope(None, "layer"): + self.assertEqual( + variable_scope.get_variable("w", []).name, + "defaultScope1_1/layer/w:0") + with variable_scope.variable_scope(None, "defaultScope"): + with variable_scope.variable_scope(None, "layer"): + self.assertEqual( + variable_scope.get_variable("w", []).name, + "defaultScope/layer/w:0") + with variable_scope.variable_scope(None, "defaultScope1"): + with variable_scope.variable_scope(None, "layer"): + self.assertEqual( + variable_scope.get_variable("w", []).name, + "defaultScope1_2/layer/w:0") + + # TODO(mihaimaruseac): calling _f fails with + # AssertionError: 'defaultScope1_3/layer/w:0' != 'defaultScope1/layer/w:0' + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testVarOpScopeUniqueNamesWithJump(self): - with self.cached_session(): - with variable_scope.variable_scope("default") as default: - with variable_scope.variable_scope(None, "layer"): - self.assertEqual( - variable_scope.get_variable("w", []).name, "default/layer/w:0") - with variable_scope.variable_scope(None, "layer"): - self.assertEqual( - variable_scope.get_variable("w", []).name, "default/layer_1/w:0") - with variable_scope.variable_scope(default): - pass - # No matter the jump in the middle, unique numbering continues. - with variable_scope.variable_scope(None, "layer"): - self.assertEqual( - variable_scope.get_variable("w", []).name, "default/layer_2/w:0") + def _f(): + with self.cached_session(): + with variable_scope.variable_scope("default") as default: + with variable_scope.variable_scope(None, "layer"): + self.assertEqual( + variable_scope.get_variable("w", []).name, "default/layer/w:0") + with variable_scope.variable_scope(None, "layer"): + self.assertEqual( + variable_scope.get_variable("w", []).name, + "default/layer_1/w:0") + with variable_scope.variable_scope(default): + pass + # No matter the jump in the middle, unique numbering continues. + with variable_scope.variable_scope(None, "layer"): + self.assertEqual( + variable_scope.get_variable("w", []).name, + "default/layer_2/w:0") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable default/layer/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testVarOpScopeReuse(self): - with self.cached_session(): - with variable_scope.variable_scope("outer") as outer: - with variable_scope.variable_scope("tower", "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/tower/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/tower/scope2/") - with variable_scope.variable_scope(None, "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/default/scope2/") - with variable_scope.variable_scope(outer, reuse=True) as outer: - with variable_scope.variable_scope("tower", "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/tower/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/tower/scope2/") - with variable_scope.variable_scope(None, "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/default/scope2/") + def _f(): + with self.cached_session(): + with variable_scope.variable_scope("outer") as outer: + with variable_scope.variable_scope("tower", "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/tower/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/tower/scope2/") + with variable_scope.variable_scope(None, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/default/scope2/") + + with variable_scope.variable_scope(outer, reuse=True) as outer: + with variable_scope.variable_scope("tower", "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/tower/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_1/tower/scope2/") + with variable_scope.variable_scope(None, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_1/default/scope2/") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable outer/tower/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # AttributeError: Tensor.op is meaningless when eager execution is enabled. def testVarScopeGetVar(self): with self.cached_session(): with variable_scope.variable_scope("root"): @@ -718,82 +839,108 @@ class VariableScopeTest(test.TestCase): variable_scope.get_variable("v", [1], dtype=dtypes.int32) self.assertEqual("dtype" in str(exc.exception), True) + @test_util.run_in_graph_and_eager_modes def testVarScopeOuterScope(self): - with self.cached_session(): - with variable_scope.variable_scope("outer") as outer: - pass - with variable_scope.variable_scope(outer): - self.assertEqual(variable_scope.get_variable("w", []).name, "outer/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/scope2/") - with variable_scope.variable_scope("default"): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/default/scope2/") - with variable_scope.variable_scope(outer, reuse=True): - self.assertEqual(variable_scope.get_variable("w", []).name, "outer/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_2/scope2/") - with variable_scope.variable_scope("default", reuse=True): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_2/default/scope2/") - - def testVarScopeNestedOuterScope(self): - with self.cached_session(): - with variable_scope.variable_scope("outer") as outer: + def _f(): + with self.cached_session(): + with variable_scope.variable_scope("outer") as outer: + pass with variable_scope.variable_scope(outer): self.assertEqual( variable_scope.get_variable("w", []).name, "outer/w:0") with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/outer/scope2/") - with variable_scope.variable_scope("default"): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/default/scope2/") + self.assertEqual(sc2, "outer_1/scope2/") + with variable_scope.variable_scope("default"): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_1/default/scope2/") with variable_scope.variable_scope(outer, reuse=True): self.assertEqual( variable_scope.get_variable("w", []).name, "outer/w:0") with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/outer_1/scope2/") - with variable_scope.variable_scope("default", reuse=True): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/default_1/scope2/") + self.assertEqual(sc2, "outer_2/scope2/") + with variable_scope.variable_scope("default", reuse=True): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_2/default/scope2/") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable outer/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes + def testVarScopeNestedOuterScope(self): + + def _f(): + with self.cached_session(): + with variable_scope.variable_scope("outer") as outer: + with variable_scope.variable_scope(outer): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/outer/scope2/") + with variable_scope.variable_scope("default"): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/default/scope2/") + + with variable_scope.variable_scope(outer, reuse=True): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/outer_1/scope2/") + with variable_scope.variable_scope("default", reuse=True): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/default_1/scope2/") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable outer/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + @test_util.run_in_graph_and_eager_modes def testVarOpScopeReuseParam(self): - with self.cached_session(): - with variable_scope.variable_scope("outer") as outer: - with variable_scope.variable_scope("tower", "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/tower/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/tower/scope2/") - with variable_scope.variable_scope(None, "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/default/scope2/") - with variable_scope.variable_scope(outer) as outer: - with variable_scope.variable_scope("tower", "default", reuse=True): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/tower/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/tower/scope2/") - outer.reuse_variables() - with variable_scope.variable_scope(None, "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/default/scope2/") + def _f(): + with self.cached_session(): + with variable_scope.variable_scope("outer") as outer: + with variable_scope.variable_scope("tower", "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/tower/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/tower/scope2/") + with variable_scope.variable_scope(None, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/default/scope2/") + + with variable_scope.variable_scope(outer) as outer: + with variable_scope.variable_scope("tower", "default", reuse=True): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/tower/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_1/tower/scope2/") + outer.reuse_variables() + with variable_scope.variable_scope(None, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_1/default/scope2/") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable outer/tower/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # AttributeError: 'variable_scope' object has no attribute + # '_graph_context_manager' def testVarOpScopeReuseError(self): with self.cached_session(): with self.assertRaises(ValueError): @@ -801,179 +948,238 @@ class VariableScopeTest(test.TestCase): self.assertEqual( variable_scope.get_variable("w", []).name, "outer/tower/w:0") + @test_util.run_in_graph_and_eager_modes def testVarOpScopeOuterScope(self): - with self.cached_session(): - with variable_scope.variable_scope("outer") as outer: - pass - with variable_scope.variable_scope(outer, "default", []): - self.assertEqual(variable_scope.get_variable("w", []).name, "outer/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/scope2/") - with variable_scope.variable_scope(None, "default", []): + + def _f(): + with self.cached_session(): + with variable_scope.variable_scope("outer") as outer: + pass + with variable_scope.variable_scope(outer, "default", []): self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") + variable_scope.get_variable("w", []).name, "outer/w:0") with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/default/scope2/") + self.assertEqual(sc2, "outer_1/scope2/") + with variable_scope.variable_scope(None, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_1/default/scope2/") - with variable_scope.variable_scope(outer, "default", reuse=True): - self.assertEqual(variable_scope.get_variable("w", []).name, "outer/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_2/scope2/") - outer.reuse_variables() - with variable_scope.variable_scope(None, "default", []): + with variable_scope.variable_scope(outer, "default", reuse=True): self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") + variable_scope.get_variable("w", []).name, "outer/w:0") with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_2/default/scope2/") + self.assertEqual(sc2, "outer_2/scope2/") + outer.reuse_variables() + with variable_scope.variable_scope(None, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_2/default/scope2/") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable outer/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + @test_util.run_in_graph_and_eager_modes def testVarOpScopeNestedOuterScope(self): - with self.cached_session(): - with variable_scope.variable_scope("outer") as outer: - with variable_scope.variable_scope(outer, "default", []): + + def _f(): + with self.cached_session(): + with variable_scope.variable_scope("outer") as outer: + with variable_scope.variable_scope(outer, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/outer/scope2/") + with variable_scope.variable_scope(None, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer/default/scope2/") + + with variable_scope.variable_scope(outer, "default", reuse=True): self.assertEqual( variable_scope.get_variable("w", []).name, "outer/w:0") with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/outer/scope2/") - with variable_scope.variable_scope(None, "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer/default/scope2/") + self.assertEqual(sc2, "outer_1/scope2/") + with variable_scope.variable_scope(None, "default", []): + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + with ops.name_scope("scope2") as sc2: + self.assertEqual(sc2, "outer_1/default/scope2/") - with variable_scope.variable_scope(outer, "default", reuse=True): - self.assertEqual(variable_scope.get_variable("w", []).name, "outer/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/scope2/") - with variable_scope.variable_scope(None, "default", []): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - with ops.name_scope("scope2") as sc2: - self.assertEqual(sc2, "outer_1/default/scope2/") + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable outer/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + @test_util.run_in_graph_and_eager_modes def testBasicWhenAuxiliaryNameScopeIsFalse(self): - with self.cached_session(): - with variable_scope.variable_scope( - "scope", auxiliary_name_scope=False) as scope: - self.assertEqual(scope.original_name_scope, "") - self.assertEqual(variable_scope.get_variable("w", []).name, "scope/w:0") - self.assertEqual(constant_op.constant([], name="c").name, "c:0") - with variable_scope.variable_scope(scope, auxiliary_name_scope=False): - self.assertEqual(scope.original_name_scope, "") - self.assertEqual( - variable_scope.get_variable("w1", []).name, "scope/w1:0") - self.assertEqual(constant_op.constant([], name="c1").name, "c1:0") - # Recheck: new name scope is NOT created before - with ops.name_scope("scope"): - self.assertEqual(constant_op.constant([], name="c").name, "scope/c:0") - with variable_scope.variable_scope("outer"): - with variable_scope.variable_scope( - "inner", auxiliary_name_scope=False) as inner: - self.assertEqual(inner.original_name_scope, "outer/") - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/inner/w:0") - self.assertEqual(constant_op.constant([], name="c").name, "outer/c:0") + def _f(): + with self.cached_session(): with variable_scope.variable_scope( - inner, auxiliary_name_scope=False) as inner1: - self.assertEqual(inner1.original_name_scope, "outer/") + "scope", auxiliary_name_scope=False) as scope: + self.assertEqual(scope.original_name_scope, "") self.assertEqual( - variable_scope.get_variable("w1", []).name, "outer/inner/w1:0") + variable_scope.get_variable("w", []).name, "scope/w:0") + self.assertEqual(constant_op.constant([], name="c").name, "c:0") + with variable_scope.variable_scope(scope, auxiliary_name_scope=False): + self.assertEqual(scope.original_name_scope, "") self.assertEqual( - constant_op.constant([], name="c1").name, "outer/c1:0") + variable_scope.get_variable("w1", []).name, "scope/w1:0") + self.assertEqual(constant_op.constant([], name="c1").name, "c1:0") # Recheck: new name scope is NOT created before - with ops.name_scope("inner"): - self.assertEqual( - constant_op.constant([], name="c").name, "outer/inner/c:0") + with ops.name_scope("scope"): + self.assertEqual(constant_op.constant([], name="c").name, "scope/c:0") + with variable_scope.variable_scope("outer"): + with variable_scope.variable_scope( + "inner", auxiliary_name_scope=False) as inner: + self.assertEqual(inner.original_name_scope, "outer/") + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/inner/w:0") + self.assertEqual( + constant_op.constant([], name="c").name, "outer/c:0") + with variable_scope.variable_scope( + inner, auxiliary_name_scope=False) as inner1: + self.assertEqual(inner1.original_name_scope, "outer/") + self.assertEqual( + variable_scope.get_variable("w1", []).name, "outer/inner/w1:0") + self.assertEqual( + constant_op.constant([], name="c1").name, "outer/c1:0") + # Recheck: new name scope is NOT created before + with ops.name_scope("inner"): + self.assertEqual( + constant_op.constant([], name="c").name, "outer/inner/c:0") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable scope/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testCreatedByDefaultNameWhenAuxiliaryNameScopeIsFalse(self): - with self.cached_session(): - with variable_scope.variable_scope( - None, default_name="default", auxiliary_name_scope=False) as scope: - self.assertEqual(scope.original_name_scope, "") - self.assertEqual( - variable_scope.get_variable("w", []).name, "default/w:0") - self.assertEqual(constant_op.constant([], name="c").name, "c:0") - # Recheck: new name scope is NOT created before - with ops.name_scope("default"): - self.assertEqual(constant_op.constant([], name="c").name, "default/c:0") - with variable_scope.variable_scope("outer"): + def _f(): + with self.cached_session(): with variable_scope.variable_scope( - None, default_name="default", auxiliary_name_scope=False) as inner: - self.assertEqual(inner.original_name_scope, "outer/") + None, default_name="default", auxiliary_name_scope=False) as scope: + self.assertEqual(scope.original_name_scope, "") self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/default/w:0") - self.assertEqual(constant_op.constant([], name="c").name, "outer/c:0") + variable_scope.get_variable("w", []).name, "default/w:0") + self.assertEqual(constant_op.constant([], name="c").name, "c:0") # Recheck: new name scope is NOT created before with ops.name_scope("default"): self.assertEqual( - constant_op.constant([], name="c").name, "outer/default/c:0") + constant_op.constant([], name="c").name, "default/c:0") + with variable_scope.variable_scope("outer"): + with variable_scope.variable_scope( + None, default_name="default", + auxiliary_name_scope=False) as inner: + self.assertEqual(inner.original_name_scope, "outer/") + self.assertEqual( + variable_scope.get_variable("w", []).name, "outer/default/w:0") + self.assertEqual( + constant_op.constant([], name="c").name, "outer/c:0") + # Recheck: new name scope is NOT created before + with ops.name_scope("default"): + self.assertEqual( + constant_op.constant([], name="c").name, "outer/default/c:0") + + # TODO(mihaimaruseac): calling _f fails with + # AssertionError: 'default_1/w:0' != 'default/w:0' + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testReenterRootScopeWhenAuxiliaryNameScopeIsFalse(self): - with self.cached_session(): - root_scope = variable_scope.get_variable_scope() - with variable_scope.variable_scope( - root_scope, auxiliary_name_scope=False) as scope: - self.assertEqual(scope.original_name_scope, "") - self.assertEqual(variable_scope.get_variable("w", []).name, "w:0") - self.assertEqual(constant_op.constant([], name="c").name, "c:0") - with variable_scope.variable_scope("outer"): + def _f(): + with self.cached_session(): + root_scope = variable_scope.get_variable_scope() with variable_scope.variable_scope( - root_scope, auxiliary_name_scope=False) as inner: - self.assertEqual(inner.original_name_scope, "") - self.assertEqual(variable_scope.get_variable("w1", []).name, "w1:0") - self.assertEqual( - constant_op.constant([], name="c1").name, "outer/c1:0") + root_scope, auxiliary_name_scope=False) as scope: + self.assertEqual(scope.original_name_scope, "") + self.assertEqual(variable_scope.get_variable("w", []).name, "w:0") + self.assertEqual(constant_op.constant([], name="c").name, "c:0") + with variable_scope.variable_scope("outer"): + with variable_scope.variable_scope( + root_scope, auxiliary_name_scope=False) as inner: + self.assertEqual(inner.original_name_scope, "") + self.assertEqual(variable_scope.get_variable("w1", []).name, "w1:0") + self.assertEqual( + constant_op.constant([], name="c1").name, "outer/c1:0") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testAuxiliaryNameScopeIsInvalid(self): - with self.cached_session(): - with self.assertRaisesRegexp(TypeError, "auxiliary_name_scope"): - with variable_scope.variable_scope( - None, default_name="scope", auxiliary_name_scope="invalid"): - pass - with self.assertRaisesRegexp(TypeError, "auxiliary_name_scope"): - with variable_scope.variable_scope( - "scope", auxiliary_name_scope="invalid"): - pass + def _f(): + with self.cached_session(): + with self.assertRaisesRegexp(TypeError, "auxiliary_name_scope"): + with variable_scope.variable_scope( + None, default_name="scope", auxiliary_name_scope="invalid"): + pass - with variable_scope.variable_scope("scope") as scope: - pass - with self.assertRaisesRegexp(TypeError, "auxiliary_name_scope"): - with variable_scope.variable_scope( - scope, auxiliary_name_scope="invalid"): + with self.assertRaisesRegexp(TypeError, "auxiliary_name_scope"): + with variable_scope.variable_scope( + "scope", auxiliary_name_scope="invalid"): + pass + + with variable_scope.variable_scope("scope") as scope: pass + with self.assertRaisesRegexp(TypeError, "auxiliary_name_scope"): + with variable_scope.variable_scope( + scope, auxiliary_name_scope="invalid"): + pass + wrap_and_execute(_f) + + @test_util.run_in_graph_and_eager_modes def testReuseScopeWithoutNameScopeCollision(self): # Github issue: #13429 - with self.cached_session(): - with variable_scope.variable_scope("outer"): - with variable_scope.variable_scope("inner") as inner: - pass - - with variable_scope.variable_scope( - inner, auxiliary_name_scope=False) as scope: - with ops.name_scope(scope.original_name_scope): - self.assertEqual( - variable_scope.get_variable("w", []).name, "outer/inner/w:0") - self.assertEqual( - constant_op.constant([], name="c").name, "outer/inner/c:0") - with ops.name_scope("inner"): - self.assertEqual(constant_op.constant([], name="c").name, "inner/c:0") + def _f(): + with self.cached_session(): + with variable_scope.variable_scope("outer"): + with variable_scope.variable_scope("inner") as inner: + pass - with variable_scope.variable_scope("another"): with variable_scope.variable_scope( - inner, auxiliary_name_scope=False) as scope1: - with ops.name_scope(scope1.original_name_scope): + inner, auxiliary_name_scope=False) as scope: + with ops.name_scope(scope.original_name_scope): self.assertEqual( - variable_scope.get_variable("w1", []).name, "outer/inner/w1:0") + variable_scope.get_variable("w", []).name, "outer/inner/w:0") self.assertEqual( - constant_op.constant([], name="c1").name, "outer/inner/c1:0") + constant_op.constant([], name="c").name, "outer/inner/c:0") with ops.name_scope("inner"): self.assertEqual( - constant_op.constant([], name="c").name, "another/inner/c:0") + constant_op.constant([], name="c").name, "inner/c:0") + with variable_scope.variable_scope("another"): + with variable_scope.variable_scope( + inner, auxiliary_name_scope=False) as scope1: + with ops.name_scope(scope1.original_name_scope): + self.assertEqual( + variable_scope.get_variable("w1", []).name, + "outer/inner/w1:0") + self.assertEqual( + constant_op.constant([], name="c1").name, "outer/inner/c1:0") + with ops.name_scope("inner"): + self.assertEqual( + constant_op.constant([], name="c").name, "another/inner/c:0") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable outer/inner/w already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one + # (different assertions failing after wrapping, in both execution modes) @test_util.run_in_graph_and_eager_modes def testGetLocalVar(self): # Check that local variable respects naming. @@ -996,30 +1202,42 @@ class VariableScopeTest(test.TestCase): self.assertEqual( variable_scope.get_local_variable("w", []).name, "outer/w:0") + @test_util.run_in_graph_and_eager_modes def testSignatureGetVarVsGetLocalVar(self): """get_{local,}variable() must take the same list of args.""" - arg_names = tf_inspect.getargspec(variable_scope.get_variable)[0] - local_arg_names = tf_inspect.getargspec( - variable_scope.get_local_variable)[0] - self.assertEqual(arg_names, local_arg_names) + def _f(): + arg_names = tf_inspect.getargspec(variable_scope.get_variable)[0] + local_arg_names = tf_inspect.getargspec( + variable_scope.get_local_variable)[0] + self.assertEqual(arg_names, local_arg_names) + + wrap_and_execute(_f) + + @test_util.run_in_graph_and_eager_modes def testGetVarWithDevice(self): - g = ops.Graph() - varname_type = [] - def device_func(op): - if op.type in ["Variable", "VariableV2", "VarHandleOp"]: - varname_type.append((op.name, op.get_attr("dtype"))) - return "/device:GPU:0" + def _f(): + g = ops.Graph() + varname_type = [] - with g.as_default(): - with ops.device(device_func): - _ = variable_scope.get_variable("x", (100, 200)) - _ = variable_scope.get_variable( - "y", dtype=dtypes.int64, initializer=numpy.arange(73)) - self.assertEqual(varname_type[0], ("x", dtypes.float32)) - self.assertEqual(varname_type[1], ("y", dtypes.int64)) + def device_func(op): + if op.type in ["Variable", "VariableV2", "VarHandleOp"]: + varname_type.append((op.name, op.get_attr("dtype"))) + return "/device:GPU:0" + with g.as_default(): + with ops.device(device_func): + _ = variable_scope.get_variable("x", (100, 200)) + _ = variable_scope.get_variable( + "y", dtype=dtypes.int64, initializer=numpy.arange(73)) + self.assertEqual(varname_type[0], ("x", dtypes.float32)) + self.assertEqual(varname_type[1], ("y", dtypes.int64)) + + wrap_and_execute(_f) + + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one def testGetCollection(self): with self.cached_session(): _ = variable_scope.get_variable("testGetCollection_a", []) @@ -1074,6 +1292,8 @@ class VariableScopeTest(test.TestCase): "testGetCollection_foo/testGetCollection_a:0" ]) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one def testGetTrainableVariablesWithGetVariable(self): with self.cached_session(): _ = variable_scope.get_variable("testGetTrainableVariables_a", []) @@ -1110,6 +1330,8 @@ class VariableScopeTest(test.TestCase): synchronization=variable_scope.VariableSynchronization.ON_READ, trainable=True) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one def testGetTrainableVariablesWithVariable(self): with self.cached_session(): _ = variable_scope.variable(1.0, name="testGetTrainableVariables_a") @@ -1149,6 +1371,8 @@ class VariableScopeTest(test.TestCase): synchronization=variable_scope.VariableSynchronization.ON_READ, trainable=True) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one def testGetGlobalVariables(self): with self.cached_session(): _ = variable_scope.get_variable("testGetGlobalVariables_a", []) @@ -1159,6 +1383,8 @@ class VariableScopeTest(test.TestCase): ["testGetGlobalVariables_foo/" "testGetGlobalVariables_b:0"]) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one def testGetLocalVariables(self): with self.cached_session(): _ = variable_scope.get_variable( @@ -1169,22 +1395,34 @@ class VariableScopeTest(test.TestCase): _ = variable_scope.get_variable("c", []) self.assertEqual([v.name for v in scope.local_variables()], ["foo/b:0"]) + @test_util.run_in_graph_and_eager_modes def testGetVariableWithRefDtype(self): - v = variable_scope.get_variable("v", shape=[3, 4], dtype=dtypes.float32) - # Ensure it is possible to do get_variable with a _ref dtype passed in. - _ = variable_scope.get_variable("w", shape=[5, 6], dtype=v.dtype) + def _f(): + v = variable_scope.get_variable("v", shape=[3, 4], dtype=dtypes.float32) + # Ensure it is possible to do get_variable with a _ref dtype passed in. + _ = variable_scope.get_variable("w", shape=[5, 6], dtype=v.dtype) + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable v already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testTwoGraphs(self): - def f(): - g1 = ops.Graph() - g2 = ops.Graph() - with g1.as_default(): - with g2.as_default(): - with variable_scope.variable_scope("_"): - pass + def _f(): + + def f(): + g1 = ops.Graph() + g2 = ops.Graph() + with g1.as_default(): + with g2.as_default(): + with variable_scope.variable_scope("_"): + pass + + self.assertRaisesRegexp(ValueError, "'_' is not a valid scope name", f) - self.assertRaisesRegexp(ValueError, "'_' is not a valid scope name", f) + wrap_and_execute(_f) def axis0_into1_partitioner(shape=None, **unused_kwargs): @@ -1206,6 +1444,8 @@ def axis0_into3_partitioner(shape=None, **unused_kwargs): class VariableScopeWithPartitioningTest(test.TestCase): + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one def testResultNameMatchesRequested(self): with variable_scope.variable_scope( "scope0", partitioner=axis0_into2_partitioner): @@ -1218,50 +1458,78 @@ class VariableScopeWithPartitioningTest(test.TestCase): self.assertIn("scope0/name0/part_1:0", [x.name for x in variables]) self.assertNotIn("scope0/name0/part_2:0", [x.name for x in variables]) + @test_util.run_in_graph_and_eager_modes def testBreaksIfPartitioningChanges(self): - with variable_scope.variable_scope( - "scope0", partitioner=axis0_into2_partitioner): - variable_scope.get_variable("name0", shape=(3, 1, 1)) - with variable_scope.variable_scope( - "scope0", partitioner=axis0_into3_partitioner, reuse=True): - with self.assertRaisesRegexp( - ValueError, - "Trying to reuse partitioned variable .* but specified partitions .* " - "and found partitions .*"): + def _f(): + with variable_scope.variable_scope( + "scope0", partitioner=axis0_into2_partitioner): variable_scope.get_variable("name0", shape=(3, 1, 1)) - with variable_scope.variable_scope( - "scope0", partitioner=axis0_into1_partitioner, reuse=True): - with self.assertRaisesRegexp( - ValueError, - "Trying to reuse partitioned variable .* but specified partitions .* " - "and found partitions .*"): - variable_scope.get_variable("name0", shape=(3, 1, 1)) + with variable_scope.variable_scope( + "scope0", partitioner=axis0_into3_partitioner, reuse=True): + with self.assertRaisesRegexp( + ValueError, + "Trying to reuse partitioned variable .* but specified partitions " + ".* and found partitions .*"): + variable_scope.get_variable("name0", shape=(3, 1, 1)) + + with variable_scope.variable_scope( + "scope0", partitioner=axis0_into1_partitioner, reuse=True): + with self.assertRaisesRegexp( + ValueError, + "Trying to reuse partitioned variable .* but specified partitions " + ".* and found partitions .*"): + variable_scope.get_variable("name0", shape=(3, 1, 1)) + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Partitioned variable with name scope0/name0 already exists. + wrap_and_execute(_f, skip_graph=True) + @test_util.run_in_graph_and_eager_modes def testReturnsExistingConcatenatedValueIfReuse(self): - with variable_scope.variable_scope( - "scope0", partitioner=axis0_into2_partitioner): - v_concat = variable_scope.get_variable("name0", shape=(3, 1, 1)) - variable_scope.get_variable_scope().reuse_variables() - v_concat_2 = variable_scope.get_variable("name0", shape=(3, 1, 1)) - self.assertEqual(v_concat, v_concat_2) + def _f(): + with variable_scope.variable_scope( + "scope0", partitioner=axis0_into2_partitioner): + v_concat = variable_scope.get_variable("name0", shape=(3, 1, 1)) + variable_scope.get_variable_scope().reuse_variables() + v_concat_2 = variable_scope.get_variable("name0", shape=(3, 1, 1)) + self.assertEqual(v_concat, v_concat_2) + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Partitioned variable with name scope0/name0 already exists. + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testAllowsReuseWithoutPartitioner(self): - with variable_scope.variable_scope( - "scope0", partitioner=axis0_into2_partitioner): - v = variable_scope.get_variable("name0", shape=(3, 1, 1)) - with variable_scope.variable_scope("scope0", reuse=True): - v_reused = variable_scope.get_variable("name0") - self.assertEqual(v, v_reused) + def _f(): + with variable_scope.variable_scope( + "scope0", partitioner=axis0_into2_partitioner): + v = variable_scope.get_variable("name0", shape=(3, 1, 1)) + with variable_scope.variable_scope("scope0", reuse=True): + v_reused = variable_scope.get_variable("name0") + self.assertEqual(v, v_reused) + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Partitioned variable with name scope0/name0 already exists. + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testPropagatePartitionerOnReopening(self): - with variable_scope.variable_scope( - "scope0", partitioner=axis0_into2_partitioner) as vs: - self.assertEqual(axis0_into2_partitioner, vs.partitioner) - with variable_scope.variable_scope(vs) as vs1: - self.assertEqual(axis0_into2_partitioner, vs1.partitioner) + def _f(): + with variable_scope.variable_scope( + "scope0", partitioner=axis0_into2_partitioner) as vs: + self.assertEqual(axis0_into2_partitioner, vs.partitioner) + with variable_scope.variable_scope(vs) as vs1: + self.assertEqual(axis0_into2_partitioner, vs1.partitioner) + + wrap_and_execute(_f) + + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # obtaining different results in the eager case compared to the graph one def testScalarIgnoresPartitioner(self): with variable_scope.variable_scope( "scope0", partitioner=axis0_into2_partitioner): @@ -1272,94 +1540,124 @@ class VariableScopeWithPartitioningTest(test.TestCase): def _testPartitionConcatenatesAlongCorrectAxis(self, use_resource): - def _part_axis_0(**unused_kwargs): - return (2, 1, 1) + def _f(): - def _part_axis_1(**unused_kwargs): - return (1, 2, 1) + def _part_axis_0(**unused_kwargs): + return (2, 1, 1) - with variable_scope.variable_scope("root", use_resource=use_resource): - v0 = variable_scope.get_variable( - "n0", shape=(2, 2, 2), partitioner=_part_axis_0) - v1 = variable_scope.get_variable( - "n1", shape=(2, 2, 2), partitioner=_part_axis_1) + def _part_axis_1(**unused_kwargs): + return (1, 2, 1) - self.assertEqual(v0.get_shape(), (2, 2, 2)) - self.assertEqual(v1.get_shape(), (2, 2, 2)) + with variable_scope.variable_scope("root", use_resource=use_resource): + v0 = variable_scope.get_variable( + "n0", shape=(2, 2, 2), partitioner=_part_axis_0) + v1 = variable_scope.get_variable( + "n1", shape=(2, 2, 2), partitioner=_part_axis_1) + + self.assertEqual(v0.get_shape(), (2, 2, 2)) + self.assertEqual(v1.get_shape(), (2, 2, 2)) + + n0_0 = list(v0)[0] + n0_1 = list(v0)[1] + self.assertEqual(n0_0.get_shape(), (1, 2, 2)) + self.assertEqual(n0_1.get_shape(), (1, 2, 2)) - n0_0 = list(v0)[0] - n0_1 = list(v0)[1] - self.assertEqual(n0_0.get_shape(), (1, 2, 2)) - self.assertEqual(n0_1.get_shape(), (1, 2, 2)) + n1_0 = list(v1)[0] + n1_1 = list(v1)[1] + self.assertEqual(n1_0.get_shape(), (2, 1, 2)) + self.assertEqual(n1_1.get_shape(), (2, 1, 2)) - n1_0 = list(v1)[0] - n1_1 = list(v1)[1] - self.assertEqual(n1_0.get_shape(), (2, 1, 2)) - self.assertEqual(n1_1.get_shape(), (2, 1, 2)) + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Partitioned variable with name root/n0 already exists. + wrap_and_execute(_f, skip_graph=True) + @test_util.run_in_graph_and_eager_modes def testPartitionConcatenatesAlongCorrectAxis(self): self._testPartitionConcatenatesAlongCorrectAxis(use_resource=False) + @test_util.run_in_graph_and_eager_modes def testPartitionConcatenatesAlongCorrectAxisResource(self): self._testPartitionConcatenatesAlongCorrectAxis(use_resource=True) class VariableScopeWithCustomGetterTest(test.TestCase): + @test_util.run_in_graph_and_eager_modes def testNonCallableGetterFails(self): - with self.assertRaisesRegexp(ValueError, r"custom_getter .* not callable:"): - with variable_scope.variable_scope("scope0", custom_getter=3): - variable_scope.get_variable("name0") - with self.assertRaisesRegexp(ValueError, r"custom_getter .* not callable:"): - variable_scope.get_variable("name0", custom_getter=3) + def _f(): + with self.assertRaisesRegexp(ValueError, + r"custom_getter .* not callable:"): + with variable_scope.variable_scope("scope0", custom_getter=3): + variable_scope.get_variable("name0") + with self.assertRaisesRegexp(ValueError, + r"custom_getter .* not callable:"): + variable_scope.get_variable("name0", custom_getter=3) + + wrap_and_execute(_f) + + @test_util.run_in_graph_and_eager_modes def testNoSideEffectsWithIdentityCustomGetter(self): - called = [0] - def custom_getter(getter, *args, **kwargs): - called[0] += 1 - return getter(*args, **kwargs) + def _f(): + called = [0] - with variable_scope.variable_scope( - "scope", custom_getter=custom_getter) as scope: - v = variable_scope.get_variable("v", [1]) - with variable_scope.variable_scope(scope, reuse=True): - v2 = variable_scope.get_variable("v", [1]) - with variable_scope.variable_scope("new_scope") as new_scope: - v3 = variable_scope.get_variable("v3", [1]) - with variable_scope.variable_scope( - new_scope, reuse=True, custom_getter=custom_getter): - v4 = variable_scope.get_variable("v3", [1]) + def custom_getter(getter, *args, **kwargs): + called[0] += 1 + return getter(*args, **kwargs) + + with variable_scope.variable_scope( + "scope", custom_getter=custom_getter) as scope: + v = variable_scope.get_variable("v", [1]) + with variable_scope.variable_scope(scope, reuse=True): + v2 = variable_scope.get_variable("v", [1]) + with variable_scope.variable_scope("new_scope") as new_scope: + v3 = variable_scope.get_variable("v3", [1]) + with variable_scope.variable_scope( + new_scope, reuse=True, custom_getter=custom_getter): + v4 = variable_scope.get_variable("v3", [1]) + + self.assertEqual(v, v2) + self.assertEqual(v3, v4) + self.assertEqual(3, called[0]) # skipped one in the first new_scope - self.assertEqual(v, v2) - self.assertEqual(v3, v4) - self.assertEqual(3, called[0]) # skipped one in the first new_scope + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable scope/v already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + @test_util.run_in_graph_and_eager_modes def testSynchronizationAndAggregationWithCustomGetter(self): - called = [0] - synchronization = variable_scope.VariableSynchronization.AUTO - aggregation = variable_scope.VariableAggregation.NONE - def custom_getter(getter, *args, **kwargs): - called[0] += 1 + def _f(): + called = [0] + synchronization = variable_scope.VariableSynchronization.AUTO + aggregation = variable_scope.VariableAggregation.NONE - # Verify synchronization and aggregation kwargs are as expected. - self.assertEqual(kwargs["synchronization"], synchronization) - self.assertEqual(kwargs["aggregation"], aggregation) - return getter(*args, **kwargs) + def custom_getter(getter, *args, **kwargs): + called[0] += 1 - with variable_scope.variable_scope("scope", custom_getter=custom_getter): - variable_scope.get_variable("v", [1]) - self.assertEqual(1, called[0]) + # Verify synchronization and aggregation kwargs are as expected. + self.assertEqual(kwargs["synchronization"], synchronization) + self.assertEqual(kwargs["aggregation"], aggregation) + return getter(*args, **kwargs) - with variable_scope.variable_scope("scope", custom_getter=custom_getter): - synchronization = variable_scope.VariableSynchronization.ON_READ - aggregation = variable_scope.VariableAggregation.MEAN - variable_scope.get_variable( - "v1", [1], synchronization=synchronization, aggregation=aggregation) + with variable_scope.variable_scope("scope", custom_getter=custom_getter): + variable_scope.get_variable("v", [1]) + self.assertEqual(1, called[0]) + + with variable_scope.variable_scope("scope", custom_getter=custom_getter): + synchronization = variable_scope.VariableSynchronization.ON_READ + aggregation = variable_scope.VariableAggregation.MEAN + variable_scope.get_variable( + "v1", [1], synchronization=synchronization, aggregation=aggregation) - self.assertEqual(2, called[0]) + self.assertEqual(2, called[0]) + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable scope/v already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + + @test_util.run_in_graph_and_eager_modes def testCustomGetterWithReuse(self): # Custom getter can choose to behave differently on reused variables. def custom_getter(getter, *args, **kwargs): @@ -1370,15 +1668,25 @@ class VariableScopeWithCustomGetterTest(test.TestCase): else: return array_ops.identity(var, name="not_reused") - with variable_scope.variable_scope( - "scope", custom_getter=custom_getter) as scope: - v = variable_scope.get_variable("v", [1]) - with variable_scope.variable_scope(scope, reuse=True): - v2 = variable_scope.get_variable("v", [1]) - - self.assertEqual(v.name, "not_reused:0") - self.assertEqual(v2.name, "reused:0") - + def _f(): + with variable_scope.variable_scope( + "scope", custom_getter=custom_getter) as scope: + v = variable_scope.get_variable("v", [1]) + with variable_scope.variable_scope(scope, reuse=True): + v2 = variable_scope.get_variable("v", [1]) + + self.assertEqual(v.name, "not_reused:0") + self.assertEqual(v2.name, "reused:0") + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable scope/v already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) + + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Fetch argument cannot be interpreted as a Tensor. (Tensor + # Tensor("custom_getter/add:0", shape=(1, 2, 3), dtype=float32) is not an + # element of this graph.) def testGetterThatCreatesTwoVariablesAndSumsThem(self): def custom_getter(getter, name, *args, **kwargs): @@ -1401,6 +1709,11 @@ class VariableScopeWithCustomGetterTest(test.TestCase): np_vars, np_v = sess.run([true_vars, v]) self.assertAllClose(np_v, sum(np_vars)) + # TODO(mihaimaruseac): Not converted to use wrap_function because of + # ValueError: Fetch argument cannot be interpreted as a Tensor. (Tensor + # Tensor("sum_getter_2/add:0", shape=(1, 2, 3), dtype=float32) is not an + # element of this graph.) def testNestedCustomGetters(self): def sum_getter(getter, name, *args, **kwargs): @@ -1444,101 +1757,125 @@ class VariableScopeWithCustomGetterTest(test.TestCase): np_v, (((np_vars[0] * np_vars[1]) + (np_vars[2] * np_vars[3])) + ( (np_vars[4] * np_vars[5]) + (np_vars[6] * np_vars[7])))) + @test_util.run_in_graph_and_eager_modes def testVariableCreator(self): - variable_names = [] + def _f(): + + variable_names = [] - def creator_a(next_creator, **kwargs): - variable_names.append(kwargs.get("name", "")) - return next_creator(**kwargs) + def creator_a(next_creator, **kwargs): + variable_names.append(kwargs.get("name", "")) + return next_creator(**kwargs) - def creator_b(next_creator, **kwargs): - kwargs["name"] = "forced_name" - return next_creator(**kwargs) + def creator_b(next_creator, **kwargs): + kwargs["name"] = "forced_name" + return next_creator(**kwargs) - with variable_scope.variable_creator_scope(creator_a): - with variable_scope.variable_creator_scope(creator_b): - variable_scope.variable(1.0, name="one_name") + with variable_scope.variable_creator_scope(creator_a): + with variable_scope.variable_creator_scope(creator_b): + variable_scope.variable(1.0, name="one_name") - self.assertAllEqual(variable_names, ["forced_name"]) + self.assertAllEqual(variable_names, ["forced_name"]) - called = [False] + called = [False] - def creater_c(next_creator, **kwargs): - called[0] = True - self.assertEqual(kwargs["synchronization"], - variable_scope.VariableSynchronization.ON_WRITE) - self.assertEqual(kwargs["aggregation"], - variable_scope.VariableAggregation.MEAN) - return next_creator(**kwargs) + def creater_c(next_creator, **kwargs): + called[0] = True + self.assertEqual(kwargs["synchronization"], + variable_scope.VariableSynchronization.ON_WRITE) + self.assertEqual(kwargs["aggregation"], + variable_scope.VariableAggregation.MEAN) + return next_creator(**kwargs) - with variable_scope.variable_creator_scope(creater_c): - variable_scope.get_variable( - "v", [], - synchronization=variable_scope.VariableSynchronization.ON_WRITE, - aggregation=variable_scope.VariableAggregation.MEAN) - self.assertTrue(called[0]) + with variable_scope.variable_creator_scope(creater_c): + variable_scope.get_variable( + "v", [], + synchronization=variable_scope.VariableSynchronization.ON_WRITE, + aggregation=variable_scope.VariableAggregation.MEAN) + self.assertTrue(called[0]) + + # TODO(mihaimaruseac): calling _f fails with + # ValueError: Variable v already exists, disallowed. + wrap_and_execute(_f, skip_graph=True) class PartitionInfoTest(test.TestCase): + @test_util.run_in_graph_and_eager_modes def testConstructorChecks(self): - # Invalid arg types. - with self.assertRaises(TypeError): - variable_scope._PartitionInfo(full_shape=None, var_offset=[0, 1]) - with self.assertRaises(TypeError): - variable_scope._PartitionInfo(full_shape=[0, 1], var_offset=None) - with self.assertRaises(TypeError): - variable_scope._PartitionInfo(full_shape="foo", var_offset=[0, 1]) - with self.assertRaises(TypeError): - variable_scope._PartitionInfo(full_shape=[0, 1], var_offset="foo") - - # full_shape and var_offset must have same length. - with self.assertRaises(ValueError): - variable_scope._PartitionInfo(full_shape=[0, 1], var_offset=[0]) - # Offset must always be less than shape. - with self.assertRaises(ValueError): - variable_scope._PartitionInfo(full_shape=[1, 1], var_offset=[0, 1]) + def _f(): + # Invalid arg types. + with self.assertRaises(TypeError): + variable_scope._PartitionInfo(full_shape=None, var_offset=[0, 1]) + with self.assertRaises(TypeError): + variable_scope._PartitionInfo(full_shape=[0, 1], var_offset=None) + with self.assertRaises(TypeError): + variable_scope._PartitionInfo(full_shape="foo", var_offset=[0, 1]) + with self.assertRaises(TypeError): + variable_scope._PartitionInfo(full_shape=[0, 1], var_offset="foo") + + # full_shape and var_offset must have same length. + with self.assertRaises(ValueError): + variable_scope._PartitionInfo(full_shape=[0, 1], var_offset=[0]) + # Offset must always be less than shape. + with self.assertRaises(ValueError): + variable_scope._PartitionInfo(full_shape=[1, 1], var_offset=[0, 1]) + + wrap_and_execute(_f) + + @test_util.run_in_graph_and_eager_modes def testSingleOffset(self): - partition_info = variable_scope._PartitionInfo( - full_shape=[9, 3], var_offset=[4, 0]) - self.assertEqual(4, partition_info.single_offset([1, 3])) - # Tests when the variable isn't partitioned at all. - partition_info = variable_scope._PartitionInfo( - full_shape=[9, 3], var_offset=[0, 0]) - self.assertEqual(0, partition_info.single_offset([9, 3])) + def _f(): + partition_info = variable_scope._PartitionInfo( + full_shape=[9, 3], var_offset=[4, 0]) + self.assertEqual(4, partition_info.single_offset([1, 3])) + + # Tests when the variable isn't partitioned at all. + partition_info = variable_scope._PartitionInfo( + full_shape=[9, 3], var_offset=[0, 0]) + self.assertEqual(0, partition_info.single_offset([9, 3])) + + wrap_and_execute(_f) + @test_util.run_in_graph_and_eager_modes def testSingleSliceDim(self): - partition_info = variable_scope._PartitionInfo( - full_shape=[9, 3], var_offset=[4, 0]) - # Invalid shape. - with self.assertRaises(TypeError): - partition_info.single_slice_dim(None) - # Rank of shape differs from full_shape. - with self.assertRaises(ValueError): - partition_info.single_slice_dim([1, 2, 3]) + def _f(): + partition_info = variable_scope._PartitionInfo( + full_shape=[9, 3], var_offset=[4, 0]) + # Invalid shape. + with self.assertRaises(TypeError): + partition_info.single_slice_dim(None) - # Shape is too large given var_offset (4+6 > 9). - with self.assertRaises(ValueError): - partition_info.single_slice_dim([6, 3]) + # Rank of shape differs from full_shape. + with self.assertRaises(ValueError): + partition_info.single_slice_dim([1, 2, 3]) - # Multiple possible slice dim from shape. - with self.assertRaises(ValueError): - partition_info.single_slice_dim([1, 1]) + # Shape is too large given var_offset (4+6 > 9). + with self.assertRaises(ValueError): + partition_info.single_slice_dim([6, 3]) + + # Multiple possible slice dim from shape. + with self.assertRaises(ValueError): + partition_info.single_slice_dim([1, 1]) + + partition_info = variable_scope._PartitionInfo( + full_shape=[9, 3], var_offset=[0, 0]) + self.assertEqual(1, partition_info.single_slice_dim([9, 2])) + partition_info = variable_scope._PartitionInfo( + full_shape=[9, 3], var_offset=[4, 0]) + self.assertEqual(0, partition_info.single_slice_dim([2, 3])) - partition_info = variable_scope._PartitionInfo( - full_shape=[9, 3], var_offset=[0, 0]) - self.assertEqual(1, partition_info.single_slice_dim([9, 2])) - partition_info = variable_scope._PartitionInfo( - full_shape=[9, 3], var_offset=[4, 0]) - self.assertEqual(0, partition_info.single_slice_dim([2, 3])) + wrap_and_execute(_f) class VariableScopeMultithreadedTest(test.TestCase): + # TODO(mihaimaruseac): Not wrapping these as they cause timeouts if wrapped + @test_util.run_in_graph_and_eager_modes def testTwoThreadsDisjointScopeEntry(self): def thread_fn(i, graph): @@ -1567,6 +1904,8 @@ class VariableScopeMultithreadedTest(test.TestCase): threads[1].start() threads[1].join() + # TODO(mihaimaruseac): Not wrapping these as they cause timeouts if wrapped + @test_util.run_in_graph_and_eager_modes def testTwoThreadsNestedScopeEntry(self): def thread_fn(i, graph, run_event, pause_event): @@ -1604,6 +1943,8 @@ class VariableScopeMultithreadedTest(test.TestCase): threads[0].join() threads[1].join() + # TODO(mihaimaruseac): Not wrapping these as they cause timeouts if wrapped + @test_util.run_in_graph_and_eager_modes def testReenterMainScope(self): def thread_fn(graph, main_thread_scope): -- GitLab From d4eb6ab275f3db5e5ea116b1a0ac4203ef01db87 Mon Sep 17 00:00:00 2001 From: Ayush Dubey Date: Tue, 16 Oct 2018 14:01:38 -0700 Subject: [PATCH 0162/1825] Fix documentation for multi-worker training. PiperOrigin-RevId: 217381397 --- tensorflow/contrib/distribute/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/contrib/distribute/README.md b/tensorflow/contrib/distribute/README.md index b416619fc1..f82453f3b5 100644 --- a/tensorflow/contrib/distribute/README.md +++ b/tensorflow/contrib/distribute/README.md @@ -190,7 +190,7 @@ in the input function gives a solid boost in performance. When using For multi-worker training, no code change is required to the `Estimator` code. You can run the same model code for all tasks in your cluster including parameter servers and the evaluator. But you need to use -`tf.estimator.train_and_evaluator`, explicitly specify `num_gpus_per_workers` +`tf.estimator.train_and_evaluate`, explicitly specify `num_gpus_per_workers` for your strategy object, and set "TF\_CONFIG" environment variables for each binary running in your cluster. We'll provide a Kubernetes template in the [tensorflow/ecosystem](https://github.com/tensorflow/ecosystem) repo which sets -- GitLab From c2250e40335e212d9fd1b035d1ea65beb61a4eca Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Tue, 16 Oct 2018 14:08:30 -0700 Subject: [PATCH 0163/1825] Update windows RBE toolchains to bazel 0.18, and update the toolchain image. PiperOrigin-RevId: 217382795 --- third_party/toolchains/preconfig/win_1803/BUILD | 2 +- .../preconfig/win_1803/{bazel_6f8e36b => bazel_018}/BUILD | 0 .../preconfig/win_1803/{bazel_6f8e36b => bazel_018}/CROSSTOOL | 0 .../win_1803/{bazel_6f8e36b => bazel_018}/dummy_toolchain.bzl | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename third_party/toolchains/preconfig/win_1803/{bazel_6f8e36b => bazel_018}/BUILD (100%) rename third_party/toolchains/preconfig/win_1803/{bazel_6f8e36b => bazel_018}/CROSSTOOL (100%) rename third_party/toolchains/preconfig/win_1803/{bazel_6f8e36b => bazel_018}/dummy_toolchain.bzl (100%) diff --git a/third_party/toolchains/preconfig/win_1803/BUILD b/third_party/toolchains/preconfig/win_1803/BUILD index 6b798dfc3a..45209d260d 100644 --- a/third_party/toolchains/preconfig/win_1803/BUILD +++ b/third_party/toolchains/preconfig/win_1803/BUILD @@ -17,7 +17,7 @@ platform( remote_execution_properties = """ properties:{ name:"container-image" - value:"docker://gcr.io/tensorflow-testing/tf-rbe-win@sha256:b2eeb661e0134ef96a4736677e8f96a90970bc206dea93739cd711031b62a0e5" + value:"docker://gcr.io/tensorflow-testing/tf-rbe-win@sha256:bd22c6bfff6afc1fa4304ec4411df2410d93645494117585332a4e2258358422" } properties:{ name: "OSFamily" value: "Windows" diff --git a/third_party/toolchains/preconfig/win_1803/bazel_6f8e36b/BUILD b/third_party/toolchains/preconfig/win_1803/bazel_018/BUILD similarity index 100% rename from third_party/toolchains/preconfig/win_1803/bazel_6f8e36b/BUILD rename to third_party/toolchains/preconfig/win_1803/bazel_018/BUILD diff --git a/third_party/toolchains/preconfig/win_1803/bazel_6f8e36b/CROSSTOOL b/third_party/toolchains/preconfig/win_1803/bazel_018/CROSSTOOL similarity index 100% rename from third_party/toolchains/preconfig/win_1803/bazel_6f8e36b/CROSSTOOL rename to third_party/toolchains/preconfig/win_1803/bazel_018/CROSSTOOL diff --git a/third_party/toolchains/preconfig/win_1803/bazel_6f8e36b/dummy_toolchain.bzl b/third_party/toolchains/preconfig/win_1803/bazel_018/dummy_toolchain.bzl similarity index 100% rename from third_party/toolchains/preconfig/win_1803/bazel_6f8e36b/dummy_toolchain.bzl rename to third_party/toolchains/preconfig/win_1803/bazel_018/dummy_toolchain.bzl -- GitLab From f024ddcda099ed233e3384c43e0b62f5c328076b Mon Sep 17 00:00:00 2001 From: Sourabh Bajaj Date: Tue, 16 Oct 2018 14:22:13 -0700 Subject: [PATCH 0164/1825] When converting the inputs to tensor the dtype should automatically be inferred from the input rather the using the layer dtype as those can be different when doing mixed precision training. PiperOrigin-RevId: 217385423 --- 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 efa21955e6..ef81ec76c3 100644 --- a/tensorflow/python/keras/layers/core.py +++ b/tensorflow/python/keras/layers/core.py @@ -956,7 +956,7 @@ class Dense(Layer): self.built = True def call(self, inputs): - inputs = ops.convert_to_tensor(inputs, dtype=self.dtype) + inputs = ops.convert_to_tensor(inputs) rank = common_shapes.rank(inputs) if rank > 2: # Broadcasting is required for the inputs. -- GitLab From 864ab17cac9bf8deb0928b1aa8e6a96ece290c78 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 14:30:39 -0700 Subject: [PATCH 0165/1825] Internal-only change PiperOrigin-RevId: 217387046 --- tensorflow/core/BUILD | 4 +++- tensorflow/core/platform/default/build_config.bzl | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 841291e6d8..4f95f207ad 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -108,6 +108,7 @@ load( "tf_additional_device_tracer_cuda_deps", "tf_additional_device_tracer_deps", "tf_additional_device_tracer_srcs", + "tf_additional_device_tracer_test_flags", "tf_additional_gdr_lib_defines", "tf_additional_human_readable_json_deps", "tf_additional_lib_defines", @@ -4654,7 +4655,8 @@ tf_cc_test_gpu( name = "device_tracer_test", size = "small", srcs = ["platform/device_tracer_test.cc"], - args = ["--heap_check=local"], + args = + ["--heap_check=local"] + tf_additional_device_tracer_test_flags(), linkstatic = tf_kernel_tests_linkstatic(), tags = tf_cuda_tests_tags() + ["nomac"], deps = [ diff --git a/tensorflow/core/platform/default/build_config.bzl b/tensorflow/core/platform/default/build_config.bzl index 927dbbc5b3..c9c89d066e 100644 --- a/tensorflow/core/platform/default/build_config.bzl +++ b/tensorflow/core/platform/default/build_config.bzl @@ -585,6 +585,9 @@ def tf_additional_device_tracer_cuda_deps(): def tf_additional_device_tracer_deps(): return [] +def tf_additional_device_tracer_test_flags(): + return [] + def tf_additional_libdevice_data(): return [] -- GitLab From 14ab986f5ff8dd28fc3a45df24f2a959b19aeb8b Mon Sep 17 00:00:00 2001 From: Alan Chiao Date: Tue, 16 Oct 2018 14:37:18 -0700 Subject: [PATCH 0166/1825] Internal change. PiperOrigin-RevId: 217388369 --- .../contrib/lite/kernels/sparse_output_fully_connected.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/sparse_output_fully_connected.cc b/tensorflow/contrib/lite/kernels/sparse_output_fully_connected.cc index 226bba2d47..66daf5e84a 100644 --- a/tensorflow/contrib/lite/kernels/sparse_output_fully_connected.cc +++ b/tensorflow/contrib/lite/kernels/sparse_output_fully_connected.cc @@ -118,9 +118,10 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { GetTemporary(context, node, /*index=*/kScalingFactors); scaling_factors->type = kTfLiteFloat32; scaling_factors->allocation_type = kTfLiteArenaRw; - TfLiteIntArray* scaling_factors_size = TfLiteIntArrayCreate(1); - scaling_factors_size->data[0] = n_batch; - if (!TfLiteIntArrayEqual(scaling_factors->dims, scaling_factors_size)) { + int scaling_dims[1] = {n_batch}; + if (!TfLiteIntArrayEqualsArray(scaling_factors->dims, 1, scaling_dims)) { + TfLiteIntArray* scaling_factors_size = TfLiteIntArrayCreate(1); + scaling_factors_size->data[0] = n_batch; TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scaling_factors, scaling_factors_size)); } -- GitLab From ad836327e4d914528ee5c542974ebfba507670dd Mon Sep 17 00:00:00 2001 From: Katherine Wu Date: Tue, 16 Oct 2018 14:40:21 -0700 Subject: [PATCH 0167/1825] Replacing legacy_init_op argument in SavedModelBuilder with main_op. PiperOrigin-RevId: 217389035 --- tensorflow/examples/saved_model/saved_model_half_plus_two.py | 2 +- tensorflow/python/estimator/estimator.py | 5 ++--- tensorflow/python/saved_model/simple_save.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tensorflow/examples/saved_model/saved_model_half_plus_two.py b/tensorflow/examples/saved_model/saved_model_half_plus_two.py index 2d1e0c6f6d..72c3b9778b 100644 --- a/tensorflow/examples/saved_model/saved_model_half_plus_two.py +++ b/tensorflow/examples/saved_model/saved_model_half_plus_two.py @@ -215,7 +215,7 @@ def _generate_saved_model_for_half_plus_two(export_dir, sess, [tf.saved_model.tag_constants.SERVING], signature_def_map=signature_def_map, assets_collection=tf.get_collection(tf.GraphKeys.ASSET_FILEPATHS), - legacy_init_op=tf.group(assign_filename_op)) + main_op=tf.group(assign_filename_op)) builder.save(as_text) diff --git a/tensorflow/python/estimator/estimator.py b/tensorflow/python/estimator/estimator.py index e6d82f0db7..3c1be9dbad 100644 --- a/tensorflow/python/estimator/estimator.py +++ b/tensorflow/python/estimator/estimator.py @@ -1033,10 +1033,9 @@ class Estimator(object): meta_graph_kwargs = dict( tags=export_tags, signature_def_map=signature_def_map, - assets_collection=ops.get_collection( - ops.GraphKeys.ASSET_FILEPATHS), + assets_collection=ops.get_collection(ops.GraphKeys.ASSET_FILEPATHS), strip_default_attrs=strip_default_attrs, - legacy_init_op=local_init_op, + main_op=local_init_op, saver=graph_saver) if save_variables: diff --git a/tensorflow/python/saved_model/simple_save.py b/tensorflow/python/saved_model/simple_save.py index 042b8fa8e2..76d6f666f6 100644 --- a/tensorflow/python/saved_model/simple_save.py +++ b/tensorflow/python/saved_model/simple_save.py @@ -81,6 +81,6 @@ def simple_save(session, export_dir, inputs, outputs, legacy_init_op=None): tags=[tag_constants.SERVING], signature_def_map=signature_def_map, assets_collection=ops.get_collection(ops.GraphKeys.ASSET_FILEPATHS), - legacy_init_op=legacy_init_op, + main_op=legacy_init_op, clear_devices=True) b.save() -- GitLab From 94ab1a9e82fb7630b1aae44984d73968508dc917 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Tue, 16 Oct 2018 14:44:33 -0700 Subject: [PATCH 0168/1825] Update the python license, as its URL has been updated. Triages one issue reported in #22741 PiperOrigin-RevId: 217389830 --- tensorflow/workspace.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 81e6676a97..25eed7afec 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -348,9 +348,9 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): name = "org_python_license", licenses = ["notice"], # Python 2.0 sha256_urls = { - "b5556e921715ddb9242c076cae3963f483aa47266c5e37ea4c187f77cc79501c": [ - "https://mirror.bazel.build/docs.python.org/2.7/_sources/license.txt", - "https://docs.python.org/2.7/_sources/license.txt", + "7ca8f169368827781684f7f20876d17b4415bbc5cb28baa4ca4652f0dda05e9f": [ + "https://mirror.bazel.build/docs.python.org/2.7/_sources/license.rst.txt", + "https://docs.python.org/2.7/_sources/license.rst.txt", ], }, ) -- GitLab From b28c5f471e486d86564669721887f284ed402ff8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 15:06:47 -0700 Subject: [PATCH 0169/1825] Move IsIdentityN with single output check out of IsIdentity. PiperOrigin-RevId: 217394520 --- tensorflow/core/grappler/costs/graph_properties.cc | 2 +- tensorflow/core/grappler/op_types.cc | 8 +++++--- tensorflow/core/grappler/op_types.h | 1 + .../core/grappler/optimizers/constant_folding.cc | 7 ++++--- .../core/grappler/optimizers/dependency_optimizer.cc | 10 +++++----- tensorflow/core/grappler/optimizers/model_pruner.cc | 2 +- .../core/grappler/optimizers/pin_to_host_optimizer.cc | 2 +- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tensorflow/core/grappler/costs/graph_properties.cc b/tensorflow/core/grappler/costs/graph_properties.cc index dd6ce0c132..6a6b14276a 100644 --- a/tensorflow/core/grappler/costs/graph_properties.cc +++ b/tensorflow/core/grappler/costs/graph_properties.cc @@ -1111,7 +1111,7 @@ class SymbolicShapeRefiner { c->output_tensors_as_shapes.resize(1); c->output_tensors_as_shapes[0] = ic->MakeShape(dims); } - } else if (IsIdentity(node)) { + } else if (IsIdentity(node) || IsIdentityNSingleInput(node)) { c->output_tensors_as_shapes.resize(1); c->output_tensors_as_shapes[0] = ic->input_tensors_as_shapes()[0]; if (c->input_tensor_protos[0] != nullptr) { diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index 1b5a215987..be7411019f 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -216,9 +216,6 @@ bool IsHistogramSummary(const NodeDef& node) { bool IsIdentity(const NodeDef& node) { const auto& op = node.op(); - if (op == "IdentityN" && node.attr().at("T").list().type_size() == 1) { - return true; - } return op == "Identity" || op == "RefIdentity"; } @@ -227,6 +224,11 @@ bool IsIdentityN(const NodeDef& node) { return op == "IdentityN"; } +bool IsIdentityNSingleInput(const NodeDef& node) { + return IsIdentityN(node) && node.attr().count("T") != 0 && + node.attr().at("T").list().type_size() == 1; +} + bool IsIgamma(const NodeDef& node) { return node.op() == "Igamma"; } bool IsIgammac(const NodeDef& node) { return node.op() == "Igammac"; } diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index d4e0159e81..92b62944b7 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -71,6 +71,7 @@ bool IsGreaterEqual(const NodeDef& node); bool IsHistogramSummary(const NodeDef& node); bool IsIdentity(const NodeDef& node); bool IsIdentityN(const NodeDef& node); +bool IsIdentityNSingleInput(const NodeDef& node); bool IsIgamma(const NodeDef& node); bool IsIgammac(const NodeDef& node); bool IsImag(const NodeDef& node); diff --git a/tensorflow/core/grappler/optimizers/constant_folding.cc b/tensorflow/core/grappler/optimizers/constant_folding.cc index 11331c9406..c963f96858 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding.cc @@ -187,7 +187,7 @@ string ConstantFolding::AddControlDependency(const string& input_name, // switch node, and use it to anchor the control dependency. auto outputs = node_map->GetOutputs(node->name()); for (const NodeDef* output : outputs) { - if (IsIdentity(*output)) { + if (IsIdentity(*output) || IsIdentityNSingleInput(*output)) { if (IsSameInput(node->input(0), input_name)) { return AsControlDependency(*output); } @@ -2200,7 +2200,7 @@ bool ConstantFolding::SimplifySwitch(GraphDef* optimized_graph, NodeDef* node) { auto fanouts = node_map_->GetOutputs(node->name()); if (fanouts.size() == 2) { for (NodeDef* fanout : fanouts) { - if (!IsIdentity(*fanout) || + if ((!IsIdentity(*fanout) && !IsIdentityNSingleInput(*fanout)) || NumNonControlOutputs(*fanout, *node_map_) > 0) { already_optimized = false; break; @@ -2679,7 +2679,8 @@ bool ConstantFolding::MulConvPushDown(NodeDef* node, bool ConstantFolding::PartialConstPropThroughIdentityN(NodeDef* node) { // Partial constant propagation through IdentityN. - if (IsIdentityN(*node) && NumNonControlInputs(*node) > 0) { + if ((IsIdentityN(*node) || IsIdentityNSingleInput(*node)) && + NumNonControlInputs(*node) > 0) { const std::set& tmp = node_map_->GetOutputs(node->name()); const std::vector consumers(tmp.begin(), tmp.end()); bool updated_graph = false; diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc index 6613768a35..0938c27b1f 100644 --- a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc @@ -57,7 +57,7 @@ bool RemoveInput(NodeDef* node, const string& input, NodeMap* node_map) { } // namespace bool DependencyOptimizer::SafeToRemoveIdentity(const NodeDef& node) const { - if (!IsIdentity(node)) { + if (!IsIdentity(node) && !IsIdentityNSingleInput(node)) { return true; } @@ -136,7 +136,7 @@ bool DependencyOptimizer::SafeToConvertToNoOp(const NodeDef& node) const { bool DependencyOptimizer::BypassingNodeIsBeneficial( const NodeDef& node, const std::vector& input_nodes, const std::vector& output_nodes) const { - const bool is_identity = IsIdentity(node); + const bool is_identity = IsIdentity(node) || IsIdentityNSingleInput(node); const int num_outputs = output_nodes.size(); const int num_inputs = node.input_size(); @@ -193,7 +193,7 @@ void DependencyOptimizer::OptimizeNode(int node_idx, 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); + const bool is_identity = IsIdentity(*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. @@ -415,8 +415,8 @@ Status DependencyOptimizer::OptimizeDependencies() { 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) || IsConstant(node) || - SafeToConvertToNoOp(node)) { + if (IsNoOp(node) || IsIdentity(node) || IsIdentityNSingleInput(node) || + IsConstant(node) || SafeToConvertToNoOp(node)) { nodes_to_simplify.PushBack(i); } } diff --git a/tensorflow/core/grappler/optimizers/model_pruner.cc b/tensorflow/core/grappler/optimizers/model_pruner.cc index 36eab4999d..c4fa162c59 100644 --- a/tensorflow/core/grappler/optimizers/model_pruner.cc +++ b/tensorflow/core/grappler/optimizers/model_pruner.cc @@ -32,7 +32,7 @@ bool IsTrivialOp(const NodeDef& node, const GraphRewriter& rewriter) { if (IsStopGradient(node)) { return true; } - if (IsIdentity(node)) { + if (IsIdentity(node) || IsIdentityNSingleInput(node)) { if (rewriter.FeedsMerge(node) || rewriter.IsDrivenBySwitch(node) || rewriter.IsDrivenByControlDependency(node) || rewriter.DrivesControlDependency(node)) { diff --git a/tensorflow/core/grappler/optimizers/pin_to_host_optimizer.cc b/tensorflow/core/grappler/optimizers/pin_to_host_optimizer.cc index 8278bf8289..db453f8521 100644 --- a/tensorflow/core/grappler/optimizers/pin_to_host_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/pin_to_host_optimizer.cc @@ -113,7 +113,7 @@ Status IsNodeOutputPortHostFriendly(const GraphView& graph, // These nodes may be optimized away downstream (even if pinned to Host), we // should (recusively) check their source. - if (IsIdentity(node)) { + if (IsIdentity(node) || IsIdentityNSingleInput(node)) { for (const auto& fanin : graph.GetFanins(node, false)) { bool fanin_candidate = false; TF_RETURN_IF_ERROR(IsNodeOutputPortHostFriendly( -- GitLab From 65d6feba5530d34aba6aed522b98fb9d55278316 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 15:11:14 -0700 Subject: [PATCH 0170/1825] Update XLA ORC compiler pieces for LLVM r344572 PiperOrigin-RevId: 217395302 --- .../compiler/xla/service/cpu/simple_orc_jit.cc | 18 +++++++++--------- .../compiler/xla/service/cpu/simple_orc_jit.h | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/simple_orc_jit.cc b/tensorflow/compiler/xla/service/cpu/simple_orc_jit.cc index 9ec0c8f657..f77641eb7d 100644 --- a/tensorflow/compiler/xla/service/cpu/simple_orc_jit.cc +++ b/tensorflow/compiler/xla/service/cpu/simple_orc_jit.cc @@ -108,15 +108,15 @@ SimpleOrcJIT::SimpleOrcJIT(const llvm::TargetOptions& target_options, [](llvm::Error Err) { cantFail(std::move(Err), "lookupFlags failed"); })), - object_layer_(execution_session_, - [this](llvm::orc::VModuleKey) { - llvm::orc::RTDyldObjectLinkingLayer::Resources result; - result.MemMgr = - std::make_shared( - orc_jit_memory_mapper::GetInstance()); - result.Resolver = symbol_resolver_; - return result; - }), + object_layer_( + execution_session_, + [this](llvm::orc::VModuleKey) { + llvm::orc::LegacyRTDyldObjectLinkingLayer::Resources result; + result.MemMgr = std::make_shared( + orc_jit_memory_mapper::GetInstance()); + result.Resolver = symbol_resolver_; + return result; + }), compile_layer_(object_layer_, CompilerFunctor(target_machine_.get(), &disassembler_, opt_level, optimize_for_size, diff --git a/tensorflow/compiler/xla/service/cpu/simple_orc_jit.h b/tensorflow/compiler/xla/service/cpu/simple_orc_jit.h index d74b63fcf4..78406ba143 100644 --- a/tensorflow/compiler/xla/service/cpu/simple_orc_jit.h +++ b/tensorflow/compiler/xla/service/cpu/simple_orc_jit.h @@ -44,9 +44,9 @@ namespace cpu { // it's added to the JIT. class SimpleOrcJIT { public: - using ObjLayerT = llvm::orc::RTDyldObjectLinkingLayer; + using ObjLayerT = llvm::orc::LegacyRTDyldObjectLinkingLayer; using CompileFtor = std::function; - using CompileLayerT = llvm::orc::IRCompileLayer; + using CompileLayerT = llvm::orc::LegacyIRCompileLayer; using VModuleKeyT = llvm::orc::VModuleKey; // Create a new JIT, targeting the host architecture. -- GitLab From 8f435f9e89f4801ecb0aa993e496e64fd7e9981a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 15:12:42 -0700 Subject: [PATCH 0171/1825] [XLA:TPU] Implementation of Gather from rank 2 tensors on TPUs. PiperOrigin-RevId: 217395583 --- tensorflow/compiler/xla/service/gather_expander.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/gather_expander.h b/tensorflow/compiler/xla/service/gather_expander.h index 2b39359aae..8af9c6b71f 100644 --- a/tensorflow/compiler/xla/service/gather_expander.h +++ b/tensorflow/compiler/xla/service/gather_expander.h @@ -28,7 +28,7 @@ class GatherExpander : public HloModulePass { absl::string_view name() const override { return "gather_expander"; } StatusOr Run(HloModule* module) override; - private: + protected: StatusOr ExpandGather(HloInstruction* gather_instr); }; -- GitLab From e4e19db364cf7ef0ac22cdf1cb55d4cdd30bec00 Mon Sep 17 00:00:00 2001 From: Adrian Kuegel Date: Tue, 16 Oct 2018 15:27:50 -0700 Subject: [PATCH 0172/1825] Support arbitrary many values in KeyValueSort on CPU backend. PiperOrigin-RevId: 217398356 --- .../compiler/xla/service/cpu/ir_emitter.cc | 119 +++++++++-------- .../xla/service/cpu/runtime_key_value_sort.cc | 123 ++++++++++-------- .../xla/service/cpu/runtime_key_value_sort.h | 60 +++++---- 3 files changed, 168 insertions(+), 134 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc index b2abdb39a5..50a8d0b1a5 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc @@ -54,6 +54,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/cpu/simple_orc_jit.h" #include "tensorflow/compiler/xla/service/elemental_ir_emitter.h" #include "tensorflow/compiler/xla/service/hlo_casting_utils.h" +#include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_instructions.h" #include "tensorflow/compiler/xla/service/hlo_opcode.h" #include "tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h" @@ -493,53 +494,44 @@ Status IrEmitter::HandleOutfeed(HloInstruction* outfeed) { return Status::OK(); } -Status IrEmitter::HandleSort(HloInstruction* sort) { +Status IrEmitter::HandleSort(HloInstruction* hlo) { + const HloSortInstruction* sort = Cast(hlo); TF_RETURN_IF_ERROR(EmitTargetAddressForOp(sort)); - auto keys = sort->operand(0); - auto values = sort->operand_count() > 1 ? sort->operand(1) : nullptr; - ShapeIndex keys_shape_index({}); - ShapeIndex values_shape_index({}); - if (values != nullptr) { - keys_shape_index = ShapeIndex({0}); - values_shape_index = ShapeIndex({1}); - } - auto keys_destination = GetAllocationSlice(*sort, keys_shape_index); - auto keys_destination_address = - EmitBufferPointer(keys_destination, keys->shape()); - auto values_destination = GetAllocationSlice(*sort, values_shape_index); - llvm::Value* values_destination_address = nullptr; - - // The sort is implemented in-place, therefore we first copy the operand - // buffer to the output buffer if they are not the same. - if (keys_destination != GetAllocationSlice(*keys)) { - int64 primitive_type_size = - ShapeUtil::ByteSizeOfPrimitiveType(keys->shape().element_type()); - auto source_buffer = GetEmittedValueFor(keys); - int64 keys_size = ByteSizeOf(keys->shape()); - MemCpy(keys_destination_address, /*DstAlign=*/primitive_type_size, - source_buffer, - /*SrcAlign=*/primitive_type_size, keys_size); - } - if (values != nullptr) { - values_destination_address = - EmitBufferPointer(values_destination, values->shape()); - if (values_destination != GetAllocationSlice(*values)) { + Shape keys_shape = sort->keys()->shape(); + std::vector destination_addresses(sort->operand_count()); + for (int64 i = 0; i < sort->operand_count(); ++i) { + ShapeIndex shape_index = + sort->values_count() > 0 ? ShapeIndex({i}) : ShapeIndex({}); + const HloInstruction* operand = sort->operand(i); + // We assume that the layout of all involved operands and outputs is the + // same. + TF_RET_CHECK( + LayoutUtil::LayoutsInShapesEqual(keys_shape, operand->shape())); + TF_RET_CHECK(LayoutUtil::LayoutsInShapesEqual( + keys_shape, ShapeUtil::GetSubshape(sort->shape(), shape_index))); + + // The sort is implemented in-place, therefore we first copy the operand + // buffer to the output buffer if they are not the same. + auto destination_buffer = GetAllocationSlice(*sort, shape_index); + destination_addresses[i] = + EmitBufferPointer(destination_buffer, operand->shape()); + auto source_address = GetAllocationSlice(*operand); + if (destination_buffer != source_address) { int64 primitive_type_size = - ShapeUtil::ByteSizeOfPrimitiveType(values->shape().element_type()); - auto source_buffer = GetEmittedValueFor(values); - int64 values_size = ByteSizeOf(values->shape()); - MemCpy(values_destination_address, /*DstAlign=*/primitive_type_size, + ShapeUtil::ByteSizeOfPrimitiveType(operand->shape().element_type()); + auto source_buffer = GetEmittedValueFor(operand); + int64 size = ByteSizeOf(operand->shape()); + MemCpy(destination_addresses[i], /*DstAlign=*/primitive_type_size, source_buffer, - /*SrcAlign=*/primitive_type_size, values_size); + /*SrcAlign=*/primitive_type_size, size); } } // Normalize the shape and the dimension to sort. Shape normalized_keys_shape = - ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( - keys->shape()); + ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout(keys_shape); int64 physical_dimension_to_sort = LayoutUtil::MakeLogicalToPhysical( - keys->shape().layout())[sort->dimensions(0)]; + keys_shape.layout())[sort->sort_dimension()]; int64 sort_dimension_elements = normalized_keys_shape.dimensions(physical_dimension_to_sort); @@ -553,7 +545,7 @@ Status IrEmitter::HandleSort(HloInstruction* sort) { lower_dimensions *= normalized_keys_shape.dimensions(i); } - PrimitiveType keys_type = keys->shape().element_type(); + PrimitiveType keys_type = keys_shape.element_type(); const char* fn_name = nullptr; llvm::Type* keys_native_type = nullptr; switch (keys_type) { @@ -614,28 +606,49 @@ Status IrEmitter::HandleSort(HloInstruction* sort) { llvm::FunctionType* key_value_sort_type = llvm::FunctionType::get( b_.getVoidTy(), {keys_native_type, b_.getInt64Ty(), b_.getInt64Ty(), b_.getInt64Ty(), - b_.getInt8PtrTy(), b_.getInt32Ty()}, + b_.getInt8PtrTy()->getPointerTo(), b_.getInt32Ty(), + b_.getInt32Ty()->getPointerTo()}, /*isVarArg=*/false); auto* key_value_sort_func = llvm::cast( module_->getOrInsertFunction(fn_name, key_value_sort_type)); key_value_sort_func->setCallingConv(llvm::CallingConv::C); key_value_sort_func->setDoesNotThrow(); key_value_sort_func->setOnlyAccessesArgMemory(); + llvm::Value* values; + llvm::Value* sizes; + if (sort->values_count() == 0) { + values = llvm::Constant::getNullValue(b_.getInt8PtrTy()->getPointerTo()); + sizes = llvm::Constant::getNullValue(b_.getInt32Ty()->getPointerTo()); + } else { + values = llvm_ir::EmitAllocaAtFunctionEntryWithCount( + b_.getInt8PtrTy(), b_.getInt32(sort->values_count()), + "cc_values_alloca", &b_); + sizes = llvm_ir::EmitAllocaAtFunctionEntryWithCount( + b_.getInt32Ty(), b_.getInt32(sort->values_count()), "cc_sizes_alloca", + &b_); + for (int64 i = 0; i < sort->values_count(); ++i) { + llvm::Value* value_as_i8ptr = + PointerCast(destination_addresses[i + 1], b_.getInt8PtrTy()); + llvm::Value* slot_in_values_alloca = + ConstInBoundsGEP1_32(b_.getInt8PtrTy(), values, i); + Store(value_as_i8ptr, slot_in_values_alloca); + llvm::Value* slot_in_sizes_alloca = + ConstInBoundsGEP1_32(b_.getInt32Ty(), sizes, i); + llvm::Value* size = b_.getInt32(ShapeUtil::ByteSizeOfPrimitiveType( + sort->operand(i + 1)->shape().element_type())); + Store(size, slot_in_sizes_alloca); + } + } + Call(key_value_sort_func, - {PointerCast(keys_destination_address, keys_native_type), + {PointerCast(destination_addresses[0], keys_native_type), b_.getInt64(higher_dimensions), b_.getInt64(sort_dimension_elements), - b_.getInt64(lower_dimensions), - values != nullptr - ? PointerCast(values_destination_address, b_.getInt8PtrTy()) - : llvm::Constant::getNullValue(b_.getInt8PtrTy()), - b_.getInt32(values != nullptr ? ShapeUtil::ByteSizeOfPrimitiveType( - values->shape().element_type()) - : 0)}); - - if (values != nullptr) { - llvm_ir::EmitTuple(GetIrArrayFor(sort), - {keys_destination_address, values_destination_address}, - &b_, module_); + b_.getInt64(lower_dimensions), values, + b_.getInt32(sort->values_count()), sizes}); + + if (sort->values_count() > 0) { + llvm_ir::EmitTuple(GetIrArrayFor(sort), destination_addresses, &b_, + module_); } return Status::OK(); } diff --git a/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.cc b/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.cc index e0e7deb98e..bbbb634f73 100644 --- a/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.cc +++ b/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.cc @@ -99,8 +99,9 @@ void KeyValueSort(std::pair* row_to_sort, } template -void KeyValueSortImpl(KeyType* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { +void KeyValueSortImpl(KeyType* keys, int64 a, int64 b, int64 c, char** values, + int32 values_count, + int32* values_primitive_type_size_in_bytes) { // High-level idea of the iteration/sorting logic: // Conceptually we have a 3-dimensional shape [a, b, c]. b corresponds to the // dimension to sort, c is the product of the more minor dimensions (set to 1 @@ -129,7 +130,7 @@ void KeyValueSortImpl(KeyType* keys, int64 a, int64 b, int64 c, char* values, index % sort_dimension_offset + (index - index % sort_dimension_offset) * sort_dimension_elements; // TODO(b/26783907): We could define a custom iterator class that references - // both arrays. Then we could avoid the intermediate copy. However this + // all arrays. Then we could avoid the intermediate copy. However this // would become more complicated, and it is not clear if the benefit is high // enough. for (int64 i = 0; i < sort_dimension_elements; ++i) { @@ -140,97 +141,109 @@ void KeyValueSortImpl(KeyType* keys, int64 a, int64 b, int64 c, char* values, for (int64 i = 0; i < sort_dimension_elements; ++i) { keys[base_offset + i * sort_dimension_offset] = row_to_sort[i].first; } - if (values == nullptr) { - continue; - } // Reorder the values according to the order defined by the keys. - for (int64 i = 0; i < sort_dimension_elements; ++i) { - int64 memory_index = - (base_offset + row_to_sort[i].second * sort_dimension_offset) * - values_primitive_type_size_in_bytes; - - reordered_values[i] = std::string(values + memory_index, - values_primitive_type_size_in_bytes); - } - for (int64 i = 0; i < sort_dimension_elements; ++i) { - int64 memory_index = (base_offset + i * sort_dimension_offset) * - values_primitive_type_size_in_bytes; - memcpy(values + memory_index, reordered_values[i].c_str(), - values_primitive_type_size_in_bytes); + for (int32 idx = 0; idx < values_count; ++idx) { + for (int64 i = 0; i < sort_dimension_elements; ++i) { + int64 memory_index = + (base_offset + row_to_sort[i].second * sort_dimension_offset) * + values_primitive_type_size_in_bytes[idx]; + + reordered_values[i] = + std::string(values[idx] + memory_index, + values_primitive_type_size_in_bytes[idx]); + } + for (int64 i = 0; i < sort_dimension_elements; ++i) { + int64 memory_index = (base_offset + i * sort_dimension_offset) * + values_primitive_type_size_in_bytes[idx]; + memcpy(values[idx] + memory_index, reordered_values[i].c_str(), + values_primitive_type_size_in_bytes[idx]); + } } } } } // namespace TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortPRED( - bool* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + bool* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortS8( - int8* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + int8* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortU8( - uint8* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + uint8* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortS16( - int16* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + int16* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortU16( - uint16* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + uint16* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortF16( - Eigen::half* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + Eigen::half* keys, int64 a, int64 b, int64 c, char** values, + int32 values_count, int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortS32( - int32* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + int32* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortU32( - uint32* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + uint32* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortF32( - float* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + float* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortS64( - int64* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + int64* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortU64( - uint64* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + uint64* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortF64( - double* keys, int64 a, int64 b, int64 c, char* values, - int32 values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); + double* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, + int32* values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_count, + values_primitive_type_size_in_bytes); } diff --git a/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.h b/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.h index 28e35e82c1..7821099386 100644 --- a/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.h +++ b/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.h @@ -22,67 +22,75 @@ limitations under the License. extern "C" { // 'keys' represents a 3-dimensional shape with dimensions [a, b, c]. The 'b' -// dimension of 'keys' is sorted into ascending order. 'values' can be nullptr. -// If 'values' is not nullptr, the elements in 'values' are reordered in such a -// way that if the element at index 'i' in 'keys' was moved to index 'j', the -// element at index 'i' in 'values' is also moved to index 'j' (which means that -// the same elements correspond to each other as before). +// dimension of 'keys' is sorted into ascending order. If 'values_count' is <= +// 0, 'values' and 'values_primitive_type_size_in_bytes' can be nullptr. +// If 'values_count' > 0, they contain exactly 'values_count' many elements. +// Each element of 'values' also represents a 3-dimensional shape with +// dimensions [a, b, c], and the size of the primitive type of the i-th shape +// has exactly 'values_primitive_type_size_in_bytes[i]' bytes. The elements in +// each 'values' shape are reordered in such a way that if the element at index +// 'i' in 'keys' was moved to index 'j', the element at index 'i' in a 'values' +// shape is also moved to index 'j' (which means that the same elements +// correspond to each other as before). extern void __xla_cpu_runtime_KeyValueSortPRED( bool* keys, tensorflow::int64 a, tensorflow::int64 b, tensorflow::int64 c, - char* values, tensorflow::int32 values_primitive_type_size_in_bytes); + char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortS8( tensorflow::int8* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortU8( tensorflow::uint8* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortS16( tensorflow::int16* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortU16( tensorflow::uint16* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortF16( Eigen::half* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortS32( tensorflow::int32* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortU32( tensorflow::uint32* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortF32( float* keys, tensorflow::int64 a, tensorflow::int64 b, tensorflow::int64 c, - char* values, tensorflow::int32 values_primitive_type_size_in_bytes); + char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortS64( tensorflow::int64* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortU64( tensorflow::uint64* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char* values, - tensorflow::int32 values_primitive_type_size_in_bytes); + tensorflow::int64 c, char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortF64( double* keys, tensorflow::int64 a, tensorflow::int64 b, tensorflow::int64 c, - char* values, tensorflow::int32 values_primitive_type_size_in_bytes); + char** values, tensorflow::int32 values_count, + tensorflow::int32* values_primitive_type_size_in_bytes); } #endif // TENSORFLOW_COMPILER_XLA_SERVICE_CPU_RUNTIME_KEY_VALUE_SORT_H_ -- GitLab From e6440a80c846ef1d29428348356d41f0d7a36eba Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Tue, 16 Oct 2018 15:37:30 -0700 Subject: [PATCH 0173/1825] [TF:XLA] Bump open source abseil revision to 5b70a8910b2e6fb0ce5193a41873139a126d2f7f PiperOrigin-RevId: 217400075 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 25eed7afec..7b11efeafc 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -121,11 +121,11 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "com_google_absl", build_file = clean_dep("//third_party:com_google_absl.BUILD"), - sha256 = "225b683f2f866cd12b868e43b5af00e032e70e3f78ff332108b0ce15d41f6091", - strip_prefix = "abseil-cpp-a00bdd176d66ef0b417d9576052a19091fbdf891", + sha256 = "4648b8738c059e6061b0dd49c87c139eb5d1e95973d790cf5fcecdbb1d6993ce", + strip_prefix = "abseil-cpp-5b70a8910b2e6fb0ce5193a41873139a126d2f7f", urls = [ - "https://mirror.bazel.build/github.com/abseil/abseil-cpp/archive/a00bdd176d66ef0b417d9576052a19091fbdf891.tar.gz", - "https://github.com/abseil/abseil-cpp/archive/a00bdd176d66ef0b417d9576052a19091fbdf891.tar.gz", + "https://mirror.bazel.build/github.com/abseil/abseil-cpp/archive/5b70a8910b2e6fb0ce5193a41873139a126d2f7f.tar.gz", + "https://github.com/abseil/abseil-cpp/archive/5b70a8910b2e6fb0ce5193a41873139a126d2f7f.tar.gz", ], ) -- GitLab From a3f855aca20d212386fd19c46adcc1bea51ceed1 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 16:14:24 -0700 Subject: [PATCH 0174/1825] Add support for batch-major input in the unidirectional LSTM Op. PiperOrigin-RevId: 217406579 --- tensorflow/contrib/lite/c/builtin_op_data.h | 5 +- .../lite/core/api/flatbuffer_conversions.cc | 2 +- .../kernels/bidirectional_sequence_lstm.cc | 30 +-- tensorflow/contrib/lite/kernels/lstm.cc | 6 +- tensorflow/contrib/lite/kernels/lstm_eval.cc | 223 ++++++++++++------ tensorflow/contrib/lite/kernels/lstm_eval.h | 20 +- .../kernels/unidirectional_sequence_lstm.cc | 18 +- .../unidirectional_sequence_lstm_test.cc | 148 +++++++++--- tensorflow/contrib/lite/schema/schema.fbs | 3 + .../contrib/lite/schema/schema_generated.h | 23 +- 10 files changed, 338 insertions(+), 140 deletions(-) diff --git a/tensorflow/contrib/lite/c/builtin_op_data.h b/tensorflow/contrib/lite/c/builtin_op_data.h index 1e65c3cee2..5a5f3ad61c 100644 --- a/tensorflow/contrib/lite/c/builtin_op_data.h +++ b/tensorflow/contrib/lite/c/builtin_op_data.h @@ -187,10 +187,13 @@ typedef struct { } TfLiteLSTMParams; typedef struct { - // Parameters for the LSTM kernel. + // Parameters needed for the underlying LSTM. TfLiteFusedActivation activation; float cell_clip; float proj_clip; + + // If set to true then the first dimension is time, otherwise batch. + bool time_major; } TfLiteUnidirectionalSequenceLSTMParams; typedef struct { diff --git a/tensorflow/contrib/lite/core/api/flatbuffer_conversions.cc b/tensorflow/contrib/lite/core/api/flatbuffer_conversions.cc index 348ce54dd7..fe56c4ebf9 100644 --- a/tensorflow/contrib/lite/core/api/flatbuffer_conversions.cc +++ b/tensorflow/contrib/lite/core/api/flatbuffer_conversions.cc @@ -399,11 +399,11 @@ TfLiteStatus ParseOpData(const Operator* op, BuiltinOperator op_type, parse_activation(seq_lstm_params->fused_activation_function()); params->cell_clip = seq_lstm_params->cell_clip(); params->proj_clip = seq_lstm_params->proj_clip(); + params->time_major = seq_lstm_params->time_major(); } *builtin_data = reinterpret_cast(params); break; } - case BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM: { auto params = allocator->AllocatePOD(); diff --git a/tensorflow/contrib/lite/kernels/bidirectional_sequence_lstm.cc b/tensorflow/contrib/lite/kernels/bidirectional_sequence_lstm.cc index 60abfbc85e..f8660fbaa2 100644 --- a/tensorflow/contrib/lite/kernels/bidirectional_sequence_lstm.cc +++ b/tensorflow/contrib/lite/kernels/bidirectional_sequence_lstm.cc @@ -876,6 +876,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { params->merge_outputs ? fw_recurrent_to_output_weights->dims->data[1] : 0; const auto actual_bw_output = params->merge_outputs ? fw_output : bw_output; + // TODO(mirkov): add batch_major support (http://b/117326122). switch (fw_input_to_output_weights->type) { case kTfLiteFloat32: { TfLiteStatus fw_pass_status = lstm_eval::EvalFloat( @@ -889,8 +890,8 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { fw_aux_input_to_output_weights, fw_input_gate_bias, fw_forget_gate_bias, fw_cell_bias, fw_output_gate_bias, fw_projection_weights, fw_projection_bias, &lstm_params, - /*forward_sequence=*/true, /*output_offset=*/0, fw_scratch_buffer, - fw_activation_state, fw_cell_state, fw_output); + /*forward_sequence=*/true, /*time_major=*/true, /*output_offset=*/0, + fw_scratch_buffer, fw_activation_state, fw_cell_state, fw_output); TF_LITE_ENSURE_OK(context, fw_pass_status); TfLiteStatus bw_pass_status = lstm_eval::EvalFloat( @@ -904,8 +905,9 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { bw_aux_input_to_output_weights, bw_input_gate_bias, bw_forget_gate_bias, bw_cell_bias, bw_output_gate_bias, bw_projection_weights, bw_projection_bias, &lstm_params, - /*forward_sequence=*/false, bw_output_offset, bw_scratch_buffer, - bw_activation_state, bw_cell_state, actual_bw_output); + /*forward_sequence=*/false, /*time_major=*/true, bw_output_offset, + bw_scratch_buffer, bw_activation_state, bw_cell_state, + actual_bw_output); TF_LITE_ENSURE_OK(context, bw_pass_status); return kTfLiteOk; } @@ -942,11 +944,11 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { fw_aux_input_to_output_weights, fw_input_gate_bias, fw_forget_gate_bias, fw_cell_bias, fw_output_gate_bias, fw_projection_weights, fw_projection_bias, &lstm_params, - /*forward_sequence=*/true, /*output_offset=*/0, fw_scratch_buffer, - scaling_factors, prod_scaling_factors, recovered_cell_weights, - input_quantized, aux_input_quantized, fw_activation_state_quantized, - fw_cell_state_quantized, fw_activation_state, fw_cell_state, - fw_output); + /*forward_sequence=*/true, /*time_major=*/true, /*output_offset=*/0, + fw_scratch_buffer, scaling_factors, prod_scaling_factors, + recovered_cell_weights, input_quantized, aux_input_quantized, + fw_activation_state_quantized, fw_cell_state_quantized, + fw_activation_state, fw_cell_state, fw_output); TF_LITE_ENSURE_OK(context, fw_pass_status); TfLiteStatus bw_pass_status = lstm_eval::EvalHybrid( @@ -960,11 +962,11 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { bw_aux_input_to_output_weights, bw_input_gate_bias, bw_forget_gate_bias, bw_cell_bias, bw_output_gate_bias, bw_projection_weights, bw_projection_bias, &lstm_params, - /*forward_sequence=*/false, bw_output_offset, bw_scratch_buffer, - scaling_factors, prod_scaling_factors, recovered_cell_weights, - input_quantized, aux_input_quantized, bw_activation_state_quantized, - bw_cell_state_quantized, bw_activation_state, bw_cell_state, - actual_bw_output); + /*forward_sequence=*/false, /*time_major=*/true, bw_output_offset, + bw_scratch_buffer, scaling_factors, prod_scaling_factors, + recovered_cell_weights, input_quantized, aux_input_quantized, + bw_activation_state_quantized, bw_cell_state_quantized, + bw_activation_state, bw_cell_state, actual_bw_output); TF_LITE_ENSURE_OK(context, bw_pass_status); return kTfLiteOk; } diff --git a/tensorflow/contrib/lite/kernels/lstm.cc b/tensorflow/contrib/lite/kernels/lstm.cc index f08a1a80c0..3666122e94 100644 --- a/tensorflow/contrib/lite/kernels/lstm.cc +++ b/tensorflow/contrib/lite/kernels/lstm.cc @@ -497,6 +497,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { /*aux_input_to_output_weights=*/nullptr, input_gate_bias, forget_gate_bias, cell_bias, output_gate_bias, projection_weights, projection_bias, params, /*forward_sequence=*/true, + /*time_major=*/true, /*output_offset=*/0, scratch_buffer, activation_state, cell_state, output); } @@ -524,8 +525,9 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { /*aux_input_to_output_weights=*/nullptr, input_gate_bias, forget_gate_bias, cell_bias, output_gate_bias, projection_weights, projection_bias, params, /*forward_sequence=*/true, - /*output_offset=*/0, scratch_buffer, scaling_factors, - prod_scaling_factors, recovered_cell_weights, input_quantized, + /*time_major=*/true, /*output_offset=*/0, scratch_buffer, + scaling_factors, prod_scaling_factors, recovered_cell_weights, + input_quantized, /*aux_input_quantized=*/nullptr, activation_state_quantized, cell_state_quantized, activation_state, cell_state, output); } diff --git a/tensorflow/contrib/lite/kernels/lstm_eval.cc b/tensorflow/contrib/lite/kernels/lstm_eval.cc index 2ef70aa933..5b7951a931 100644 --- a/tensorflow/contrib/lite/kernels/lstm_eval.cc +++ b/tensorflow/contrib/lite/kernels/lstm_eval.cc @@ -710,9 +710,10 @@ TfLiteStatus EvalFloat( const TfLiteTensor* input_gate_bias, const TfLiteTensor* forget_gate_bias, const TfLiteTensor* cell_bias, const TfLiteTensor* output_gate_bias, const TfLiteTensor* projection_weights, const TfLiteTensor* projection_bias, - const TfLiteLSTMParams* params, bool forward_sequence, int output_offset, - TfLiteTensor* scratch_buffer, TfLiteTensor* activation_state, - TfLiteTensor* cell_state, TfLiteTensor* output) { + const TfLiteLSTMParams* params, bool forward_sequence, bool time_major, + int output_offset, TfLiteTensor* scratch_buffer, + TfLiteTensor* activation_state, TfLiteTensor* cell_state, + TfLiteTensor* output) { TF_LITE_ASSERT(input->dims->size >= 2 && input->dims->size <= 3); const int max_time = (input->dims->size == 2) ? 1 : input->dims->data[0]; const int n_batch = input->dims->data[input->dims->size - 2]; @@ -777,36 +778,71 @@ TfLiteStatus EvalFloat( aux_input_to_output_weights_ptr = aux_input_to_output_weights->data.f; } - // Loop through the sequence. const int output_batch_leading_dim = output->dims->data[output->dims->size - 1]; - const int input_step = n_batch * n_input; - const int output_step = n_batch * output_batch_leading_dim; - for (int t = 0; t < max_time; t++) { - // If this is the forward_sequence, step forward, otherwise step backwards. - const int t_rel = forward_sequence ? t : max_time - t - 1; - const float* input_ptr = input->data.f + t_rel * input_step; - if (aux_input) { - aux_input_ptr = aux_input->data.f + t_rel * input_step; + if (time_major) { + // Loop through the sequence. + const int input_step = n_batch * n_input; + const int output_step = n_batch * output_batch_leading_dim; + for (int t = 0; t < max_time; t++) { + // If this is the forward_sequence, step forward, otherwise step + // backwards. + const int t_rel = forward_sequence ? t : max_time - t - 1; + const float* input_ptr = input->data.f + t_rel * input_step; + if (aux_input) { + aux_input_ptr = aux_input->data.f + t_rel * input_step; + } + float* output_ptr_time = + output->data.f + t_rel * output_step + output_offset; + + LstmStepWithAuxInput( + input_ptr, input_to_input_weights_ptr, + input_to_forget_weights->data.f, input_to_cell_weights->data.f, + input_to_output_weights->data.f, aux_input_ptr, + aux_input_to_input_weights_ptr, aux_input_to_forget_weights_ptr, + aux_input_to_cell_weights_ptr, aux_input_to_output_weights_ptr, + recurrent_to_input_weights_ptr, recurrent_to_forget_weights->data.f, + recurrent_to_cell_weights->data.f, + recurrent_to_output_weights->data.f, cell_to_input_weights_ptr, + cell_to_forget_weights_ptr, cell_to_output_weights_ptr, + input_gate_bias_ptr, forget_gate_bias->data.f, cell_bias->data.f, + output_gate_bias->data.f, projection_weights_ptr, projection_bias_ptr, + params, n_batch, n_cell, n_input, aux_input_size, n_output, + output_batch_leading_dim, activation_state->data.f, + cell_state->data.f, input_gate_scratch, forget_gate_scratch, + cell_scratch, output_gate_scratch, output_ptr_time); } - float* output_ptr_time = - output->data.f + t_rel * output_step + output_offset; + } else { + for (int b = 0; b < n_batch; b++) { + const int input_step = n_input; + const int output_step = output_batch_leading_dim; + for (int t = 0; t < max_time; t++) { + // If this is the forward_sequence, step forward, otherwise step + // backwards. + const int t_rel = forward_sequence ? t : max_time - t - 1; + const float* input_ptr = input->data.f + t_rel * input_step; + float* output_ptr_time = + output->data.f + t_rel * output_step + output_offset; - LstmStepWithAuxInput( - input_ptr, input_to_input_weights_ptr, input_to_forget_weights->data.f, - input_to_cell_weights->data.f, input_to_output_weights->data.f, - aux_input_ptr, aux_input_to_input_weights_ptr, - aux_input_to_forget_weights_ptr, aux_input_to_cell_weights_ptr, - aux_input_to_output_weights_ptr, recurrent_to_input_weights_ptr, - recurrent_to_forget_weights->data.f, recurrent_to_cell_weights->data.f, - recurrent_to_output_weights->data.f, cell_to_input_weights_ptr, - cell_to_forget_weights_ptr, cell_to_output_weights_ptr, - input_gate_bias_ptr, forget_gate_bias->data.f, cell_bias->data.f, - output_gate_bias->data.f, projection_weights_ptr, projection_bias_ptr, - params, n_batch, n_cell, n_input, aux_input_size, n_output, - output_batch_leading_dim, activation_state->data.f, cell_state->data.f, - input_gate_scratch, forget_gate_scratch, cell_scratch, - output_gate_scratch, output_ptr_time); + LstmStepWithAuxInput( + input_ptr, input_to_input_weights_ptr, + input_to_forget_weights->data.f, input_to_cell_weights->data.f, + input_to_output_weights->data.f, aux_input_ptr, + aux_input_to_input_weights_ptr, aux_input_to_forget_weights_ptr, + aux_input_to_cell_weights_ptr, aux_input_to_output_weights_ptr, + recurrent_to_input_weights_ptr, recurrent_to_forget_weights->data.f, + recurrent_to_cell_weights->data.f, + recurrent_to_output_weights->data.f, cell_to_input_weights_ptr, + cell_to_forget_weights_ptr, cell_to_output_weights_ptr, + input_gate_bias_ptr, forget_gate_bias->data.f, cell_bias->data.f, + output_gate_bias->data.f, projection_weights_ptr, + projection_bias_ptr, params, /*n_batch=*/1, n_cell, n_input, + aux_input_size, n_output, output_batch_leading_dim, + activation_state->data.f, cell_state->data.f, input_gate_scratch, + forget_gate_scratch, cell_scratch, output_gate_scratch, + output_ptr_time); + } + } } return kTfLiteOk; } @@ -830,13 +866,13 @@ TfLiteStatus EvalHybrid( const TfLiteTensor* input_gate_bias, const TfLiteTensor* forget_gate_bias, const TfLiteTensor* cell_bias, const TfLiteTensor* output_gate_bias, const TfLiteTensor* projection_weights, const TfLiteTensor* projection_bias, - const TfLiteLSTMParams* params, bool forward_sequence, int output_offset, - TfLiteTensor* scratch_buffer, TfLiteTensor* scaling_factors, - TfLiteTensor* prod_scaling_factors, TfLiteTensor* recovered_cell_weights, - TfLiteTensor* input_quantized, TfLiteTensor* aux_input_quantized, - TfLiteTensor* output_state_quantized, TfLiteTensor* cell_state_quantized, - TfLiteTensor* output_state, TfLiteTensor* cell_state, - TfLiteTensor* output) { + const TfLiteLSTMParams* params, bool forward_sequence, bool time_major, + int output_offset, TfLiteTensor* scratch_buffer, + TfLiteTensor* scaling_factors, TfLiteTensor* prod_scaling_factors, + TfLiteTensor* recovered_cell_weights, TfLiteTensor* input_quantized, + TfLiteTensor* aux_input_quantized, TfLiteTensor* output_state_quantized, + TfLiteTensor* cell_state_quantized, TfLiteTensor* output_state, + TfLiteTensor* cell_state, TfLiteTensor* output) { TF_LITE_ASSERT(input->dims->size >= 2 && input->dims->size <= 3); const int max_time = (input->dims->size == 2) ? 1 : input->dims->data[0]; const int n_batch = input->dims->data[input->dims->size - 2]; @@ -990,45 +1026,90 @@ TfLiteStatus EvalHybrid( aux_input_to_output_weights->params.scale; } - // Feed the sequence into the LSTM step-by-step. const int output_batch_leading_dim = output->dims->data[output->dims->size - 1]; - const int input_step = n_batch * n_input; - const int output_step = n_batch * output_batch_leading_dim; - for (int t = 0; t < max_time; t++) { - // If this is the forward_sequence, step forward, otherwise step backwards. - const int t_rel = forward_sequence ? t : max_time - t - 1; - const float* input_ptr = input->data.f + t_rel * input_step; - if (aux_input) { - aux_input_ptr = aux_input->data.f + t_rel * input_step; + if (time_major) { + // Feed the sequence into the LSTM step-by-step. + const int input_step = n_batch * n_input; + const int output_step = n_batch * output_batch_leading_dim; + for (int t = 0; t < max_time; t++) { + // If this is the forward_sequence, step forward, otherwise step + // backwards. + const int t_rel = forward_sequence ? t : max_time - t - 1; + const float* input_ptr = input->data.f + t_rel * input_step; + if (aux_input) { + aux_input_ptr = aux_input->data.f + t_rel * input_step; + } + float* output_ptr = output->data.f + t_rel * output_step + output_offset; + + LstmStepWithAuxInput( + input_ptr, input_to_input_weights_ptr, input_to_input_weights_scale, + input_to_forget_weights_ptr, input_to_forget_weights_scale, + input_to_cell_weights_ptr, input_to_cell_weights_scale, + input_to_output_weights_ptr, input_to_output_weights_scale, + aux_input_ptr, aux_input_to_input_weights_ptr, + aux_input_to_input_weights_scale, aux_input_to_forget_weights_ptr, + aux_input_to_forget_weights_scale, aux_input_to_cell_weights_ptr, + aux_input_to_cell_weights_scale, aux_input_to_output_weights_ptr, + aux_input_to_output_weights_scale, recurrent_to_input_weights_ptr, + recurrent_to_input_weights_scale, recurrent_to_forget_weights_ptr, + recurrent_to_forget_weights_scale, recurrent_to_cell_weights_ptr, + recurrent_to_cell_weights_scale, recurrent_to_output_weights_ptr, + recurrent_to_output_weights_scale, cell_to_input_weights_ptr, + cell_to_input_weights_scale, cell_to_forget_weights_ptr, + cell_to_forget_weights_scale, cell_to_output_weights_ptr, + cell_to_output_weights_scale, input_gate_bias_ptr, + forget_gate_bias_ptr, cell_bias_ptr, output_gate_bias_ptr, + projection_weights_ptr, projection_weights_scale, projection_bias_ptr, + params, n_batch, n_cell, n_input, aux_input_size, n_output, + output_batch_leading_dim, input_gate_scratch, forget_gate_scratch, + cell_scratch, output_gate_scratch, scaling_factors_ptr, + prod_scaling_factors_ptr, recovered_cell_weights_ptr, + quantized_input_ptr, quantized_aux_input_ptr, + quantized_output_state_ptr, quantized_cell_state_ptr, + output_state_ptr, cell_state_ptr, output_ptr); } - float* output_ptr = output->data.f + t_rel * output_step + output_offset; + } else { + for (int b = 0; b < n_batch; b++) { + const int input_step = n_input; + const int output_step = output_batch_leading_dim; + for (int t = 0; t < max_time; t++) { + // If this is the forward_sequence, step forward, otherwise step + // backwards. + const int t_rel = forward_sequence ? t : max_time - t - 1; + const float* input_ptr = input->data.f + t_rel * input_step; + float* output_ptr = + output->data.f + t_rel * output_step + output_offset; - LstmStepWithAuxInput( - input_ptr, input_to_input_weights_ptr, input_to_input_weights_scale, - input_to_forget_weights_ptr, input_to_forget_weights_scale, - input_to_cell_weights_ptr, input_to_cell_weights_scale, - input_to_output_weights_ptr, input_to_output_weights_scale, - aux_input_ptr, aux_input_to_input_weights_ptr, - aux_input_to_input_weights_scale, aux_input_to_forget_weights_ptr, - aux_input_to_forget_weights_scale, aux_input_to_cell_weights_ptr, - aux_input_to_cell_weights_scale, aux_input_to_output_weights_ptr, - aux_input_to_output_weights_scale, recurrent_to_input_weights_ptr, - recurrent_to_input_weights_scale, recurrent_to_forget_weights_ptr, - recurrent_to_forget_weights_scale, recurrent_to_cell_weights_ptr, - recurrent_to_cell_weights_scale, recurrent_to_output_weights_ptr, - recurrent_to_output_weights_scale, cell_to_input_weights_ptr, - cell_to_input_weights_scale, cell_to_forget_weights_ptr, - cell_to_forget_weights_scale, cell_to_output_weights_ptr, - cell_to_output_weights_scale, input_gate_bias_ptr, forget_gate_bias_ptr, - cell_bias_ptr, output_gate_bias_ptr, projection_weights_ptr, - projection_weights_scale, projection_bias_ptr, params, n_batch, n_cell, - n_input, aux_input_size, n_output, output_batch_leading_dim, - input_gate_scratch, forget_gate_scratch, cell_scratch, - output_gate_scratch, scaling_factors_ptr, prod_scaling_factors_ptr, - recovered_cell_weights_ptr, quantized_input_ptr, - quantized_aux_input_ptr, quantized_output_state_ptr, - quantized_cell_state_ptr, output_state_ptr, cell_state_ptr, output_ptr); + LstmStepWithAuxInput( + input_ptr, input_to_input_weights_ptr, input_to_input_weights_scale, + input_to_forget_weights_ptr, input_to_forget_weights_scale, + input_to_cell_weights_ptr, input_to_cell_weights_scale, + input_to_output_weights_ptr, input_to_output_weights_scale, + aux_input_ptr, aux_input_to_input_weights_ptr, + aux_input_to_input_weights_scale, aux_input_to_forget_weights_ptr, + aux_input_to_forget_weights_scale, aux_input_to_cell_weights_ptr, + aux_input_to_cell_weights_scale, aux_input_to_output_weights_ptr, + aux_input_to_output_weights_scale, recurrent_to_input_weights_ptr, + recurrent_to_input_weights_scale, recurrent_to_forget_weights_ptr, + recurrent_to_forget_weights_scale, recurrent_to_cell_weights_ptr, + recurrent_to_cell_weights_scale, recurrent_to_output_weights_ptr, + recurrent_to_output_weights_scale, cell_to_input_weights_ptr, + cell_to_input_weights_scale, cell_to_forget_weights_ptr, + cell_to_forget_weights_scale, cell_to_output_weights_ptr, + cell_to_output_weights_scale, input_gate_bias_ptr, + forget_gate_bias_ptr, cell_bias_ptr, output_gate_bias_ptr, + projection_weights_ptr, projection_weights_scale, + projection_bias_ptr, params, n_batch, n_cell, n_input, + aux_input_size, n_output, output_batch_leading_dim, + input_gate_scratch, forget_gate_scratch, cell_scratch, + output_gate_scratch, scaling_factors_ptr, prod_scaling_factors_ptr, + recovered_cell_weights_ptr, quantized_input_ptr, + quantized_aux_input_ptr, quantized_output_state_ptr, + quantized_cell_state_ptr, output_state_ptr, cell_state_ptr, + output_ptr); + } + } } return kTfLiteOk; diff --git a/tensorflow/contrib/lite/kernels/lstm_eval.h b/tensorflow/contrib/lite/kernels/lstm_eval.h index adf8cf0f64..8d8b97aead 100644 --- a/tensorflow/contrib/lite/kernels/lstm_eval.h +++ b/tensorflow/contrib/lite/kernels/lstm_eval.h @@ -42,9 +42,10 @@ TfLiteStatus EvalFloat( const TfLiteTensor* input_gate_bias, const TfLiteTensor* forget_gate_bias, const TfLiteTensor* cell_bias, const TfLiteTensor* output_gate_bias, const TfLiteTensor* projection_weights, const TfLiteTensor* projection_bias, - const TfLiteLSTMParams* params, bool forward_sequence, int output_offset, - TfLiteTensor* scratch_buffer, TfLiteTensor* activation_state, - TfLiteTensor* cell_state, TfLiteTensor* output); + const TfLiteLSTMParams* params, bool forward_sequence, bool time_major, + int output_offset, TfLiteTensor* scratch_buffer, + TfLiteTensor* activation_state, TfLiteTensor* cell_state, + TfLiteTensor* output); TfLiteStatus EvalHybrid( const TfLiteTensor* input, const TfLiteTensor* input_to_input_weights, @@ -65,12 +66,13 @@ TfLiteStatus EvalHybrid( const TfLiteTensor* input_gate_bias, const TfLiteTensor* forget_gate_bias, const TfLiteTensor* cell_bias, const TfLiteTensor* output_gate_bias, const TfLiteTensor* projection_weights, const TfLiteTensor* projection_bias, - const TfLiteLSTMParams* params, bool forward_sequence, int output_offset, - TfLiteTensor* scratch_buffer, TfLiteTensor* scaling_factors, - TfLiteTensor* prod_scaling_factors, TfLiteTensor* recovered_cell_weights, - TfLiteTensor* input_quantized, TfLiteTensor* aux_input_quantized, - TfLiteTensor* output_state_quantized, TfLiteTensor* cell_state_quantized, - TfLiteTensor* output_state, TfLiteTensor* cell_state, TfLiteTensor* output); + const TfLiteLSTMParams* params, bool forward_sequence, bool time_major, + int output_offset, TfLiteTensor* scratch_buffer, + TfLiteTensor* scaling_factors, TfLiteTensor* prod_scaling_factors, + TfLiteTensor* recovered_cell_weights, TfLiteTensor* input_quantized, + TfLiteTensor* aux_input_quantized, TfLiteTensor* output_state_quantized, + TfLiteTensor* cell_state_quantized, TfLiteTensor* output_state, + TfLiteTensor* cell_state, TfLiteTensor* output); } // namespace lstm_eval } // namespace builtin diff --git a/tensorflow/contrib/lite/kernels/unidirectional_sequence_lstm.cc b/tensorflow/contrib/lite/kernels/unidirectional_sequence_lstm.cc index 40029779e0..bd6d4d1f88 100644 --- a/tensorflow/contrib/lite/kernels/unidirectional_sequence_lstm.cc +++ b/tensorflow/contrib/lite/kernels/unidirectional_sequence_lstm.cc @@ -260,8 +260,11 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { const TfLiteTensor* input = GetInput(context, node, kInputTensor); TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32); TF_LITE_ENSURE(context, input->dims->size > 1); - const int max_time = input->dims->data[0]; - const int n_batch = input->dims->data[1]; + const auto* params = + reinterpret_cast( + node->builtin_data); + const bool time_major = params->time_major; + const int n_batch = time_major ? input->dims->data[1] : input->dims->data[0]; const int n_input = input->dims->data[2]; const TfLiteTensor* input_to_output_weights = @@ -296,10 +299,8 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_EQ(context, NumElements(cell_state), n_batch * n_cell); // Resize the output tensors. - TfLiteIntArray* output_size = TfLiteIntArrayCreate(3); - output_size->data[0] = max_time; - output_size->data[1] = n_batch; - output_size->data[2] = n_output; + TfLiteIntArray* output_size = TfLiteIntArrayCopy(input->dims); + output_size->data[input->dims->size - 1] = n_output; TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, output, output_size)); @@ -436,6 +437,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { const auto* params = reinterpret_cast( node->builtin_data); + const bool time_major = params->time_major; const TfLiteTensor* input = GetInput(context, node, kInputTensor); const TfLiteTensor* input_to_input_weights = @@ -506,7 +508,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { /*aux_input_to_cell_weights=*/nullptr, /*aux_input_to_output_weights=*/nullptr, input_gate_bias, forget_gate_bias, cell_bias, output_gate_bias, projection_weights, - projection_bias, &lstm_params, /*forward_sequence=*/true, + projection_bias, &lstm_params, /*forward_sequence=*/true, time_major, /*output_offset=*/0, scratch_buffer, activation_state, cell_state, output); } @@ -533,7 +535,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { /*aux_input_to_cell_weights=*/nullptr, /*aux_input_to_output_weights=*/nullptr, input_gate_bias, forget_gate_bias, cell_bias, output_gate_bias, projection_weights, - projection_bias, &lstm_params, /*forward_sequence=*/true, + projection_bias, &lstm_params, /*forward_sequence=*/true, time_major, /*output_offset=*/0, scratch_buffer, scaling_factors, prod_scaling_factors, recovered_cell_weights, input_quantized, /*aux_input_quantized=*/nullptr, activation_state_quantized, diff --git a/tensorflow/contrib/lite/kernels/unidirectional_sequence_lstm_test.cc b/tensorflow/contrib/lite/kernels/unidirectional_sequence_lstm_test.cc index 7b9d66c19b..1de14dd60d 100644 --- a/tensorflow/contrib/lite/kernels/unidirectional_sequence_lstm_test.cc +++ b/tensorflow/contrib/lite/kernels/unidirectional_sequence_lstm_test.cc @@ -32,7 +32,7 @@ using ::testing::ElementsAreArray; class UnidirectionalLSTMOpModel : public SingleOpModel { public: UnidirectionalLSTMOpModel(int n_batch, int n_input, int n_cell, int n_output, - int sequence_length, bool use_cifg, + int sequence_length, bool time_major, bool use_cifg, bool use_peephole, bool use_projection_weights, bool use_projection_bias, float cell_clip, float proj_clip, @@ -110,12 +110,12 @@ class UnidirectionalLSTMOpModel : public SingleOpModel { output_ = AddOutput(TensorType_FLOAT32); - SetBuiltinOp( - BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_LSTM, - BuiltinOptions_UnidirectionalSequenceLSTMOptions, - CreateUnidirectionalSequenceLSTMOptions( - builder_, ActivationFunctionType_TANH, cell_clip, proj_clip) - .Union()); + SetBuiltinOp(BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_LSTM, + BuiltinOptions_UnidirectionalSequenceLSTMOptions, + CreateUnidirectionalSequenceLSTMOptions( + builder_, ActivationFunctionType_TANH, cell_clip, + proj_clip, time_major) + .Union()); BuildInterpreter(input_shapes); } @@ -241,12 +241,12 @@ class HybridUnidirectionalLSTMOpModel : public UnidirectionalLSTMOpModel { public: HybridUnidirectionalLSTMOpModel( int n_batch, int n_input, int n_cell, int n_output, int sequence_length, - bool use_cifg, bool use_peephole, bool use_projection_weights, - bool use_projection_bias, float cell_clip, float proj_clip, - const std::vector>& input_shapes) + bool time_major, bool use_cifg, bool use_peephole, + bool use_projection_weights, bool use_projection_bias, float cell_clip, + float proj_clip, const std::vector>& input_shapes) : UnidirectionalLSTMOpModel( - n_batch, n_input, n_cell, n_output, sequence_length, use_cifg, - use_peephole, use_projection_weights, use_projection_bias, + n_batch, n_input, n_cell, n_output, sequence_length, time_major, + use_cifg, use_peephole, use_projection_weights, use_projection_bias, cell_clip, proj_clip, input_shapes, TensorType_UINT8) {} void SetInputToInputWeights(const std::vector& f) { @@ -326,21 +326,32 @@ class BaseLstmTest : public ::testing::Test { // Compares output up to tolerance to the result of the lstm given the input. void VerifyGoldens(const std::vector>& input, const std::vector>& output, - UnidirectionalLSTMOpModel* lstm, float tolerance = 1e-5) { + UnidirectionalLSTMOpModel* lstm, float tolerance = 1e-5, + bool time_major = true) { const int num_batches = input.size(); EXPECT_GT(num_batches, 0); const int num_inputs = lstm->num_inputs(); EXPECT_GT(num_inputs, 0); const int input_sequence_size = input[0].size() / num_inputs; EXPECT_GT(input_sequence_size, 0); - // Feed the whole sequence as input. - for (int i = 0; i < input_sequence_size; ++i) { + if (time_major) { + // Feed the whole sequence as input. + for (int i = 0; i < input_sequence_size; ++i) { + for (int b = 0; b < num_batches; ++b) { + const float* batch_start = input[b].data() + i * num_inputs; + const float* batch_end = batch_start + num_inputs; + + lstm->SetInput(((i * num_batches) + b) * num_inputs, batch_start, + batch_end); + } + } + } else { for (int b = 0; b < num_batches; ++b) { - const float* batch_start = input[b].data() + i * num_inputs; - const float* batch_end = batch_start + num_inputs; + const float* batch_start = input[b].data(); + const float* batch_end = batch_start + input_sequence_size * num_inputs; - lstm->SetInput(((i * num_batches) + b) * lstm->num_inputs(), - batch_start, batch_end); + lstm->SetInput(b * input_sequence_size * num_inputs, batch_start, + batch_end); } } @@ -349,15 +360,25 @@ class BaseLstmTest : public ::testing::Test { const int num_outputs = lstm->num_outputs(); EXPECT_GT(num_outputs, 0); std::vector expected; - for (int i = 0; i < input_sequence_size; ++i) { + + if (time_major) { + for (int i = 0; i < input_sequence_size; ++i) { + for (int b = 0; b < num_batches; ++b) { + const float* golden_start_batch = output[b].data() + i * num_outputs; + const float* golden_end_batch = golden_start_batch + num_outputs; + + expected.insert(expected.end(), golden_start_batch, golden_end_batch); + } + } + } else { for (int b = 0; b < num_batches; ++b) { - const float* golden_start_batch = output[b].data() + i * num_outputs; - const float* golden_end_batch = golden_start_batch + num_outputs; + const float* golden_batch_start = output[b].data(); + const float* golden_batch_end = + golden_batch_start + input_sequence_size * num_outputs; - expected.insert(expected.end(), golden_start_batch, golden_end_batch); + expected.insert(expected.end(), golden_batch_start, golden_batch_end); } } - EXPECT_THAT(lstm->GetOutput(), ElementsAreArray(ArrayFloatNear(expected, tolerance))); } @@ -422,7 +443,7 @@ TEST_F(NoCifgNoPeepholeNoProjectionNoClippingLstmTest, LstmBlackBoxTest) { UnidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, - /*use_cifg=*/false, /*use_peephole=*/false, + /*time_major=*/true, /*use_cifg=*/false, /*use_peephole=*/false, /*use_projection_weights=*/false, /*use_projection_bias=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, @@ -473,6 +494,73 @@ TEST_F(NoCifgNoPeepholeNoProjectionNoClippingLstmTest, LstmBlackBoxTest) { VerifyGoldens(lstm_input_, lstm_golden_output_, &lstm); } +TEST_F(NoCifgNoPeepholeNoProjectionNoClippingLstmTest, + LstmBlackBoxTestBatchMajor) { + 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; + + UnidirectionalLSTMOpModel lstm( + n_batch, n_input, n_cell, n_output, sequence_length, + /*time_major=*/true, /*use_cifg=*/false, /*use_peephole=*/false, + /*use_projection_weights=*/false, + /*use_projection_bias=*/false, + /*cell_clip=*/0.0, /*proj_clip=*/0.0, + { + {sequence_length, n_batch, n_input}, // input tensor + + {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 + }); + + lstm.SetInputToInputWeights(input_to_input_weights_); + lstm.SetInputToCellWeights(input_to_cell_weights_); + lstm.SetInputToForgetWeights(input_to_forget_weights_); + lstm.SetInputToOutputWeights(input_to_output_weights_); + + lstm.SetInputGateBias(input_gate_bias_); + lstm.SetCellBias(cell_gate_bias_); + lstm.SetForgetGateBias(forget_gate_bias_); + lstm.SetOutputGateBias(output_gate_bias_); + + lstm.SetRecurrentToInputWeights(recurrent_to_input_weights_); + lstm.SetRecurrentToCellWeights(recurrent_to_cell_weights_); + lstm.SetRecurrentToForgetWeights(recurrent_to_forget_weights_); + lstm.SetRecurrentToOutputWeights(recurrent_to_output_weights_); + + // Reshuffle input and output to batch major format. + std::vector> input; + std::vector> output; + + VerifyGoldens(lstm_input_, lstm_golden_output_, &lstm, /*tolerance=*/1e-5, + /*time_major=*/false); +} + TEST_F(NoCifgNoPeepholeNoProjectionNoClippingLstmTest, HybridLstmBlackBoxTest) { const int n_batch = 1; const int n_input = 2; @@ -483,7 +571,7 @@ TEST_F(NoCifgNoPeepholeNoProjectionNoClippingLstmTest, HybridLstmBlackBoxTest) { HybridUnidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, - /*use_cifg=*/false, /*use_peephole=*/false, + /*time_major=*/true, /*use_cifg=*/false, /*use_peephole=*/false, /*use_projection_weights=*/false, /*use_projection_bias=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, { @@ -591,7 +679,7 @@ TEST_F(CifgPeepholeNoProjectionNoClippingLstmTest, LstmBlackBoxTest) { UnidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, - /*use_cifg=*/true, /*use_peephole=*/true, + /*time_major=*/true, /*use_cifg=*/true, /*use_peephole=*/true, /*use_projection_weights=*/false, /*use_projection_bias=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, @@ -652,7 +740,7 @@ TEST_F(CifgPeepholeNoProjectionNoClippingLstmTest, HybridLstmBlackBoxTest) { HybridUnidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, - /*use_cifg=*/true, /*use_peephole=*/true, + /*time_major=*/true, /*use_cifg=*/true, /*use_peephole=*/true, /*use_projection_weights=*/false, /*use_projection_bias=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, @@ -1311,7 +1399,7 @@ TEST_F(NoCifgPeepholeProjectionClippingLstmTest, LstmBlackBoxTest) { UnidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, - /*use_cifg=*/false, /*use_peephole=*/true, + /*time_major=*/true, /*use_cifg=*/false, /*use_peephole=*/true, /*use_projection_weights=*/true, /*use_projection_bias=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, @@ -1377,7 +1465,7 @@ TEST_F(NoCifgPeepholeProjectionClippingLstmTest, HybridLstmBlackBoxTest) { HybridUnidirectionalLSTMOpModel lstm( n_batch, n_input, n_cell, n_output, sequence_length, - /*use_cifg=*/false, /*use_peephole=*/true, + /*time_major=*/true, /*use_cifg=*/false, /*use_peephole=*/true, /*use_projection_weights=*/true, /*use_projection_bias=*/false, /*cell_clip=*/0.0, /*proj_clip=*/0.0, diff --git a/tensorflow/contrib/lite/schema/schema.fbs b/tensorflow/contrib/lite/schema/schema.fbs index fe3dc56e65..3045351f22 100644 --- a/tensorflow/contrib/lite/schema/schema.fbs +++ b/tensorflow/contrib/lite/schema/schema.fbs @@ -407,6 +407,9 @@ table UnidirectionalSequenceLSTMOptions { fused_activation_function:ActivationFunctionType; cell_clip: float; // Optional, 0.0 means no clipping proj_clip: float; // Optional, 0.0 means no clipping + + // If true then first dimension is sequence, otherwise batch. + time_major:bool; } table BidirectionalSequenceLSTMOptions { diff --git a/tensorflow/contrib/lite/schema/schema_generated.h b/tensorflow/contrib/lite/schema/schema_generated.h index 4426b7d407..2bae6d72ec 100755 --- a/tensorflow/contrib/lite/schema/schema_generated.h +++ b/tensorflow/contrib/lite/schema/schema_generated.h @@ -3534,10 +3534,12 @@ struct UnidirectionalSequenceLSTMOptionsT : public flatbuffers::NativeTable { ActivationFunctionType fused_activation_function; float cell_clip; float proj_clip; + bool time_major; UnidirectionalSequenceLSTMOptionsT() : fused_activation_function(ActivationFunctionType_NONE), cell_clip(0.0f), - proj_clip(0.0f) { + proj_clip(0.0f), + time_major(false) { } }; @@ -3546,7 +3548,8 @@ struct UnidirectionalSequenceLSTMOptions FLATBUFFERS_FINAL_CLASS : private flatb enum { VT_FUSED_ACTIVATION_FUNCTION = 4, VT_CELL_CLIP = 6, - VT_PROJ_CLIP = 8 + VT_PROJ_CLIP = 8, + VT_TIME_MAJOR = 10 }; ActivationFunctionType fused_activation_function() const { return static_cast(GetField(VT_FUSED_ACTIVATION_FUNCTION, 0)); @@ -3557,11 +3560,15 @@ struct UnidirectionalSequenceLSTMOptions FLATBUFFERS_FINAL_CLASS : private flatb float proj_clip() const { return GetField(VT_PROJ_CLIP, 0.0f); } + bool time_major() const { + return GetField(VT_TIME_MAJOR, 0) != 0; + } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyField(verifier, VT_FUSED_ACTIVATION_FUNCTION) && VerifyField(verifier, VT_CELL_CLIP) && VerifyField(verifier, VT_PROJ_CLIP) && + VerifyField(verifier, VT_TIME_MAJOR) && verifier.EndTable(); } UnidirectionalSequenceLSTMOptionsT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; @@ -3581,6 +3588,9 @@ struct UnidirectionalSequenceLSTMOptionsBuilder { void add_proj_clip(float proj_clip) { fbb_.AddElement(UnidirectionalSequenceLSTMOptions::VT_PROJ_CLIP, proj_clip, 0.0f); } + void add_time_major(bool time_major) { + fbb_.AddElement(UnidirectionalSequenceLSTMOptions::VT_TIME_MAJOR, static_cast(time_major), 0); + } explicit UnidirectionalSequenceLSTMOptionsBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); @@ -3597,10 +3607,12 @@ inline flatbuffers::Offset CreateUnidirection flatbuffers::FlatBufferBuilder &_fbb, ActivationFunctionType fused_activation_function = ActivationFunctionType_NONE, float cell_clip = 0.0f, - float proj_clip = 0.0f) { + float proj_clip = 0.0f, + bool time_major = false) { UnidirectionalSequenceLSTMOptionsBuilder builder_(_fbb); builder_.add_proj_clip(proj_clip); builder_.add_cell_clip(cell_clip); + builder_.add_time_major(time_major); builder_.add_fused_activation_function(fused_activation_function); return builder_.Finish(); } @@ -8060,6 +8072,7 @@ inline void UnidirectionalSequenceLSTMOptions::UnPackTo(UnidirectionalSequenceLS { auto _e = fused_activation_function(); _o->fused_activation_function = _e; }; { auto _e = cell_clip(); _o->cell_clip = _e; }; { auto _e = proj_clip(); _o->proj_clip = _e; }; + { auto _e = time_major(); _o->time_major = _e; }; } inline flatbuffers::Offset UnidirectionalSequenceLSTMOptions::Pack(flatbuffers::FlatBufferBuilder &_fbb, const UnidirectionalSequenceLSTMOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher) { @@ -8073,11 +8086,13 @@ inline flatbuffers::Offset CreateUnidirection auto _fused_activation_function = _o->fused_activation_function; auto _cell_clip = _o->cell_clip; auto _proj_clip = _o->proj_clip; + auto _time_major = _o->time_major; return tflite::CreateUnidirectionalSequenceLSTMOptions( _fbb, _fused_activation_function, _cell_clip, - _proj_clip); + _proj_clip, + _time_major); } inline BidirectionalSequenceLSTMOptionsT *BidirectionalSequenceLSTMOptions::UnPack(const flatbuffers::resolver_function_t *_resolver) const { -- GitLab From cc0cf49a0d0cfdb23073810260ca1af480d08850 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 16:20:58 -0700 Subject: [PATCH 0175/1825] Check for the presence of a Worker machine when reassigning hooks in distributed training jobs. PiperOrigin-RevId: 217407558 --- tensorflow/python/estimator/estimator.py | 6 ++ tensorflow/python/estimator/estimator_test.py | 61 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/tensorflow/python/estimator/estimator.py b/tensorflow/python/estimator/estimator.py index 3c1be9dbad..c44413090a 100644 --- a/tensorflow/python/estimator/estimator.py +++ b/tensorflow/python/estimator/estimator.py @@ -1423,7 +1423,13 @@ class Estimator(object): # evaluations. save_summary_steps = self._config.save_summary_steps log_step_count_steps = self._config.log_step_count_steps + + # Check existence of appropriate cluster spec fields, as well as master and + # worker nodes. As master also performs evaluation, summary writing must + # occur on a different node. The presence of a worker is also checked to + # prevent reassigning hooks for single-replica jobs with just a master node. if (self._config.cluster_spec and self._config.cluster_spec.jobs and + (run_config.TaskType.WORKER in self._config.cluster_spec.jobs) and (run_config.TaskType.MASTER in self._config.cluster_spec.jobs)): # Update config values to prevent the default hooks from being created on # the master or other workers. diff --git a/tensorflow/python/estimator/estimator_test.py b/tensorflow/python/estimator/estimator_test.py index 246dfb1a4b..c26b3e6509 100644 --- a/tensorflow/python/estimator/estimator_test.py +++ b/tensorflow/python/estimator/estimator_test.py @@ -1063,6 +1063,67 @@ class EstimatorTrainTest(test.TestCase): self.assertEqual(0, mock_sess.call_args[1]['save_summaries_steps']) self.assertIsNone(mock_sess.call_args[1]['log_step_count_steps']) + def test_master_hooks_single_replica(self): + tf_config = json.dumps({ + 'cluster': { + run_config.TaskType.MASTER: ['localhost:1234'] + }, + 'task': { + 'type': run_config.TaskType.MASTER, + 'index': 0 + } + }) + with test.mock.patch.dict('os.environ', {'TF_CONFIG': tf_config}): + est = estimator.Estimator( + model_fn=model_fn_global_step_incrementer, + config=run_config.RunConfig( + save_summary_steps=100, log_step_count_steps=200)) + + with test.mock.patch.object(training, + 'MonitoredTrainingSession') as mock_sess: + est.train(dummy_input_fn, steps=1) + self.assertFalse( + any( + isinstance(hook, basic_session_run_hooks.SummarySaverHook) + for hook in mock_sess.call_args[1]['hooks'])) + self.assertFalse( + any( + isinstance(hook, basic_session_run_hooks.StepCounterHook) + for hook in mock_sess.call_args[1]['hooks'])) + self.assertEqual(100, mock_sess.call_args[1]['save_summaries_steps']) + self.assertEqual(200, mock_sess.call_args[1]['log_step_count_steps']) + + def test_master_hooks_single_replica_with_ps(self): + tf_config = json.dumps({ + 'cluster': { + run_config.TaskType.MASTER: ['localhost:1234'], + run_config.TaskType.PS: ['localhost: 1235'], + }, + 'task': { + 'type': run_config.TaskType.MASTER, + 'index': 0 + } + }) + with test.mock.patch.dict('os.environ', {'TF_CONFIG': tf_config}): + est = estimator.Estimator( + model_fn=model_fn_global_step_incrementer, + config=run_config.RunConfig( + save_summary_steps=100, log_step_count_steps=200)) + + with test.mock.patch.object(training, + 'MonitoredTrainingSession') as mock_sess: + est.train(dummy_input_fn, steps=1) + self.assertFalse( + any( + isinstance(hook, basic_session_run_hooks.SummarySaverHook) + for hook in mock_sess.call_args[1]['hooks'])) + self.assertFalse( + any( + isinstance(hook, basic_session_run_hooks.StepCounterHook) + for hook in mock_sess.call_args[1]['hooks'])) + self.assertEqual(100, mock_sess.call_args[1]['save_summaries_steps']) + self.assertEqual(200, mock_sess.call_args[1]['log_step_count_steps']) + def _model_fn_with_eval_metric_ops(features, labels, mode, params): _, _ = features, labels -- GitLab From 500a807939be51396af56f59fcb4e8e569697a22 Mon Sep 17 00:00:00 2001 From: Benjamin Kramer Date: Tue, 16 Oct 2018 16:23:06 -0700 Subject: [PATCH 0176/1825] [TF:XLA] Bump open source llvm revision to r344639 PiperOrigin-RevId: 217407969 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 7b11efeafc..19a4631d8f 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -481,11 +481,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 = "a4f8bfe7e3e69069934a87e612a1d4d3b8b6af13e0f1213a42a6046e1bcd50d8", - strip_prefix = "llvm-d3429e96fe1e45b1dc0106463832523f37faf271", + sha256 = "b5bd6aa6613f8d57cb5973d43b9d6b82def80bad66f51387d2ed9c76d2652040", + strip_prefix = "llvm-4998e62d5745cca132cf92cec718be0746e70bcf", urls = [ - "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/d3429e96fe1e45b1dc0106463832523f37faf271.tar.gz", - "https://github.com/llvm-mirror/llvm/archive/d3429e96fe1e45b1dc0106463832523f37faf271.tar.gz", + "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/4998e62d5745cca132cf92cec718be0746e70bcf.tar.gz", + "https://github.com/llvm-mirror/llvm/archive/4998e62d5745cca132cf92cec718be0746e70bcf.tar.gz", ], ) -- GitLab From adb904fb99ec706dbbe11b4e35c227ff3bb7127c Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Tue, 16 Oct 2018 17:12:32 -0700 Subject: [PATCH 0177/1825] [XLA:GPU] Actually move cudnn_fused_conv_rewriter_test out of `tests` directory. This makes it consistent with most of the other tests for individual passes, which this one morally is (even if its implementation is a little unusual since it runs the whole pass pipeline). PiperOrigin-RevId: 217415531 --- tensorflow/compiler/xla/service/gpu/BUILD | 19 +++++++++++++++++++ .../cudnn_fused_conv_rewriter_test.cc | 0 .../compiler/xla/service/gpu/tests/BUILD | 15 --------------- 3 files changed, 19 insertions(+), 15 deletions(-) rename tensorflow/compiler/xla/service/gpu/{tests => }/cudnn_fused_conv_rewriter_test.cc (100%) diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index 4eb5739fe2..449fd919d6 100644 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -25,6 +25,10 @@ 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", @@ -980,3 +984,18 @@ cc_library( "//tensorflow/core:stream_executor_no_cuda", ], ) + +tf_cc_test( + name = "cudnn_fused_conv_rewriter_test", + srcs = ["cudnn_fused_conv_rewriter_test.cc"], + tags = tf_cuda_tests_tags(), + deps = [ + "//tensorflow/compiler/xla/service:hlo_parser", + "//tensorflow/compiler/xla/service/gpu:ir_emission_utils", + "//tensorflow/compiler/xla/service/gpu/tests:gpu_codegen_test", + "//tensorflow/compiler/xla/tests:hlo_test_base", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "@com_google_absl//absl/strings", + ], +) diff --git a/tensorflow/compiler/xla/service/gpu/tests/cudnn_fused_conv_rewriter_test.cc b/tensorflow/compiler/xla/service/gpu/cudnn_fused_conv_rewriter_test.cc similarity index 100% rename from tensorflow/compiler/xla/service/gpu/tests/cudnn_fused_conv_rewriter_test.cc rename to tensorflow/compiler/xla/service/gpu/cudnn_fused_conv_rewriter_test.cc diff --git a/tensorflow/compiler/xla/service/gpu/tests/BUILD b/tensorflow/compiler/xla/service/gpu/tests/BUILD index 32eebad3b1..ed46f08d59 100644 --- a/tensorflow/compiler/xla/service/gpu/tests/BUILD +++ b/tensorflow/compiler/xla/service/gpu/tests/BUILD @@ -210,21 +210,6 @@ tf_cc_test( ], ) -tf_cc_test( - name = "cudnn_fused_conv_rewriter_test", - srcs = ["cudnn_fused_conv_rewriter_test.cc"], - tags = tf_cuda_tests_tags(), - deps = [ - ":gpu_codegen_test", - "//tensorflow/compiler/xla/service:hlo_parser", - "//tensorflow/compiler/xla/service/gpu:ir_emission_utils", - "//tensorflow/compiler/xla/tests:hlo_test_base", - "//tensorflow/core:test", - "//tensorflow/core:test_main", - "@com_google_absl//absl/strings", - ], -) - tf_cc_test( name = "gpu_atomic_test", srcs = ["gpu_atomic_test.cc"], -- GitLab From 599e37f66d08296fb3cf401377e473c34b114ea3 Mon Sep 17 00:00:00 2001 From: Sreeni Kesavarapu Date: Tue, 16 Oct 2018 17:14:22 -0700 Subject: [PATCH 0178/1825] Update the doc with the details about the rounding mode used in quantize_and_dequantize_v2. PiperOrigin-RevId: 217415800 --- .../core/api_def/base_api/api_def_QuantizeAndDequantizeV2.pbtxt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/core/api_def/base_api/api_def_QuantizeAndDequantizeV2.pbtxt b/tensorflow/core/api_def/base_api/api_def_QuantizeAndDequantizeV2.pbtxt index 9b500d0b58..c43142599b 100644 --- a/tensorflow/core/api_def/base_api/api_def_QuantizeAndDequantizeV2.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_QuantizeAndDequantizeV2.pbtxt @@ -93,5 +93,7 @@ following to each value in the 'input' tensor. output = round(clamp(value, input_min, input_max) * scale_factor) / scale_factor. +The above round function uses half to even rounding. + END } -- GitLab From 3716b1b91af0dd019102616b63eb31af62a2e743 Mon Sep 17 00:00:00 2001 From: Peter Ma Date: Tue, 16 Oct 2018 17:20:03 -0700 Subject: [PATCH 0179/1825] Move ReadyNodeManagerFactory() out of VirtualScheduler class and change it to return std::unique_ptr. PiperOrigin-RevId: 217416514 --- .../core/grappler/clusters/virtual_cluster.cc | 7 +++++-- .../core/grappler/clusters/virtual_cluster.h | 4 ++-- .../costs/analytical_cost_estimator.cc | 4 +--- .../core/grappler/costs/virtual_scheduler.cc | 18 +++++++++++++++++- .../core/grappler/costs/virtual_scheduler.h | 6 +++++- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/tensorflow/core/grappler/clusters/virtual_cluster.cc b/tensorflow/core/grappler/clusters/virtual_cluster.cc index 295b3c12e6..dbd8f26c28 100644 --- a/tensorflow/core/grappler/clusters/virtual_cluster.cc +++ b/tensorflow/core/grappler/clusters/virtual_cluster.cc @@ -34,8 +34,11 @@ VirtualCluster::VirtualCluster( VirtualCluster::VirtualCluster( const std::unordered_map& devices, - OpLevelCostEstimator* node_estimator, ReadyNodeManager* node_manager) - : Cluster(0), node_estimator_(node_estimator), node_manager_(node_manager) { + std::unique_ptr node_estimator, + std::unique_ptr node_manager) + : Cluster(0), + node_estimator_(std::move(node_estimator)), + node_manager_(std::move(node_manager)) { devices_ = devices; } diff --git a/tensorflow/core/grappler/clusters/virtual_cluster.h b/tensorflow/core/grappler/clusters/virtual_cluster.h index 6adb0b99bc..d19e39cd29 100644 --- a/tensorflow/core/grappler/clusters/virtual_cluster.h +++ b/tensorflow/core/grappler/clusters/virtual_cluster.h @@ -34,8 +34,8 @@ class VirtualCluster : public Cluster { public: VirtualCluster(const std::unordered_map& devices); VirtualCluster(const std::unordered_map& devices, - OpLevelCostEstimator* node_estimator, - ReadyNodeManager* node_manager); + std::unique_ptr node_estimator, + std::unique_ptr node_manager); VirtualCluster(const DeviceSet* device_set); ~VirtualCluster() override; diff --git a/tensorflow/core/grappler/costs/analytical_cost_estimator.cc b/tensorflow/core/grappler/costs/analytical_cost_estimator.cc index 8a6d575956..b7804ffaa5 100644 --- a/tensorflow/core/grappler/costs/analytical_cost_estimator.cc +++ b/tensorflow/core/grappler/costs/analytical_cost_estimator.cc @@ -104,9 +104,7 @@ AnalyticalCostEstimator::AnalyticalCostEstimator(Cluster* cluster, bool use_static_shapes) : AnalyticalCostEstimator( cluster, absl::make_unique(), - std::unique_ptr( - VirtualScheduler::ReadyNodeManagerFactory("FirstReady")), - use_static_shapes, nullptr) {} + ReadyNodeManagerFactory("FirstReady"), use_static_shapes, nullptr) {} AnalyticalCostEstimator::AnalyticalCostEstimator( Cluster* cluster, std::unique_ptr node_estimator, diff --git a/tensorflow/core/grappler/costs/virtual_scheduler.cc b/tensorflow/core/grappler/costs/virtual_scheduler.cc index d3c4686b32..ba50e55538 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler.cc +++ b/tensorflow/core/grappler/costs/virtual_scheduler.cc @@ -275,7 +275,23 @@ bool CompositeNodeManager::Empty() const { return empty && send_manager_.Empty() && recv_manager_.Empty(); } -// TODO(pcma): Modify to return unique_ptr instead +std::unique_ptr ReadyNodeManagerFactory( + const string& ready_node_manager) { + if (ready_node_manager == "FIFO") { + return absl::make_unique(); + } else if (ready_node_manager == "LIFO") { + return absl::make_unique(); + } else if (ready_node_manager == "FirstReady") { + return absl::make_unique(); + } else if (ready_node_manager == "Composite") { + return absl::make_unique(); + } + LOG(FATAL) << "Not a valid ready node manager: " << ready_node_manager; + return nullptr; +} + +// TODO(pcma): Delete this deprecated API after power_analyzer.cc is modeified +// to use the new factory API ReadyNodeManager* VirtualScheduler::ReadyNodeManagerFactory( const string& ready_node_manager) { if (ready_node_manager == "FIFO") { diff --git a/tensorflow/core/grappler/costs/virtual_scheduler.h b/tensorflow/core/grappler/costs/virtual_scheduler.h index 59ab0a67a8..89dff9686d 100644 --- a/tensorflow/core/grappler/costs/virtual_scheduler.h +++ b/tensorflow/core/grappler/costs/virtual_scheduler.h @@ -248,6 +248,10 @@ class CompositeNodeManager : public ReadyNodeManager { const NodeDef* curr_node_; }; +// Constructs a ready node manager from the given string. +std::unique_ptr ReadyNodeManagerFactory( + const string& ready_node_manager); + // The virtual scheduler emulates execution of nodes in a graph, considering // dependencies, device, etc. class VirtualScheduler { @@ -287,7 +291,7 @@ class VirtualScheduler { // of the virtual execution of the graph. void GenerateRunMetadata(RunMetadata* metadata); - // Methods called from constructor. + // DEPRECATED static ReadyNodeManager* ReadyNodeManagerFactory( const string& ready_node_manager); -- GitLab From 78ba89a89d39892b0cc1ef0e31a12c978e879966 Mon Sep 17 00:00:00 2001 From: Adrian Kuegel Date: Tue, 16 Oct 2018 17:24:24 -0700 Subject: [PATCH 0180/1825] Automated rollback of commit e4e19db364cf7ef0ac22cdf1cb55d4cdd30bec00 PiperOrigin-RevId: 217417068 --- .../compiler/xla/service/cpu/ir_emitter.cc | 119 ++++++++--------- .../xla/service/cpu/runtime_key_value_sort.cc | 123 ++++++++---------- .../xla/service/cpu/runtime_key_value_sort.h | 60 ++++----- 3 files changed, 134 insertions(+), 168 deletions(-) diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc index 50a8d0b1a5..b2abdb39a5 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc @@ -54,7 +54,6 @@ limitations under the License. #include "tensorflow/compiler/xla/service/cpu/simple_orc_jit.h" #include "tensorflow/compiler/xla/service/elemental_ir_emitter.h" #include "tensorflow/compiler/xla/service/hlo_casting_utils.h" -#include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/service/hlo_instructions.h" #include "tensorflow/compiler/xla/service/hlo_opcode.h" #include "tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h" @@ -494,44 +493,53 @@ Status IrEmitter::HandleOutfeed(HloInstruction* outfeed) { return Status::OK(); } -Status IrEmitter::HandleSort(HloInstruction* hlo) { - const HloSortInstruction* sort = Cast(hlo); +Status IrEmitter::HandleSort(HloInstruction* sort) { TF_RETURN_IF_ERROR(EmitTargetAddressForOp(sort)); - Shape keys_shape = sort->keys()->shape(); - std::vector destination_addresses(sort->operand_count()); - for (int64 i = 0; i < sort->operand_count(); ++i) { - ShapeIndex shape_index = - sort->values_count() > 0 ? ShapeIndex({i}) : ShapeIndex({}); - const HloInstruction* operand = sort->operand(i); - // We assume that the layout of all involved operands and outputs is the - // same. - TF_RET_CHECK( - LayoutUtil::LayoutsInShapesEqual(keys_shape, operand->shape())); - TF_RET_CHECK(LayoutUtil::LayoutsInShapesEqual( - keys_shape, ShapeUtil::GetSubshape(sort->shape(), shape_index))); - - // The sort is implemented in-place, therefore we first copy the operand - // buffer to the output buffer if they are not the same. - auto destination_buffer = GetAllocationSlice(*sort, shape_index); - destination_addresses[i] = - EmitBufferPointer(destination_buffer, operand->shape()); - auto source_address = GetAllocationSlice(*operand); - if (destination_buffer != source_address) { + auto keys = sort->operand(0); + auto values = sort->operand_count() > 1 ? sort->operand(1) : nullptr; + ShapeIndex keys_shape_index({}); + ShapeIndex values_shape_index({}); + if (values != nullptr) { + keys_shape_index = ShapeIndex({0}); + values_shape_index = ShapeIndex({1}); + } + auto keys_destination = GetAllocationSlice(*sort, keys_shape_index); + auto keys_destination_address = + EmitBufferPointer(keys_destination, keys->shape()); + auto values_destination = GetAllocationSlice(*sort, values_shape_index); + llvm::Value* values_destination_address = nullptr; + + // The sort is implemented in-place, therefore we first copy the operand + // buffer to the output buffer if they are not the same. + if (keys_destination != GetAllocationSlice(*keys)) { + int64 primitive_type_size = + ShapeUtil::ByteSizeOfPrimitiveType(keys->shape().element_type()); + auto source_buffer = GetEmittedValueFor(keys); + int64 keys_size = ByteSizeOf(keys->shape()); + MemCpy(keys_destination_address, /*DstAlign=*/primitive_type_size, + source_buffer, + /*SrcAlign=*/primitive_type_size, keys_size); + } + if (values != nullptr) { + values_destination_address = + EmitBufferPointer(values_destination, values->shape()); + if (values_destination != GetAllocationSlice(*values)) { int64 primitive_type_size = - ShapeUtil::ByteSizeOfPrimitiveType(operand->shape().element_type()); - auto source_buffer = GetEmittedValueFor(operand); - int64 size = ByteSizeOf(operand->shape()); - MemCpy(destination_addresses[i], /*DstAlign=*/primitive_type_size, + ShapeUtil::ByteSizeOfPrimitiveType(values->shape().element_type()); + auto source_buffer = GetEmittedValueFor(values); + int64 values_size = ByteSizeOf(values->shape()); + MemCpy(values_destination_address, /*DstAlign=*/primitive_type_size, source_buffer, - /*SrcAlign=*/primitive_type_size, size); + /*SrcAlign=*/primitive_type_size, values_size); } } // Normalize the shape and the dimension to sort. Shape normalized_keys_shape = - ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout(keys_shape); + ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( + keys->shape()); int64 physical_dimension_to_sort = LayoutUtil::MakeLogicalToPhysical( - keys_shape.layout())[sort->sort_dimension()]; + keys->shape().layout())[sort->dimensions(0)]; int64 sort_dimension_elements = normalized_keys_shape.dimensions(physical_dimension_to_sort); @@ -545,7 +553,7 @@ Status IrEmitter::HandleSort(HloInstruction* hlo) { lower_dimensions *= normalized_keys_shape.dimensions(i); } - PrimitiveType keys_type = keys_shape.element_type(); + PrimitiveType keys_type = keys->shape().element_type(); const char* fn_name = nullptr; llvm::Type* keys_native_type = nullptr; switch (keys_type) { @@ -606,49 +614,28 @@ Status IrEmitter::HandleSort(HloInstruction* hlo) { llvm::FunctionType* key_value_sort_type = llvm::FunctionType::get( b_.getVoidTy(), {keys_native_type, b_.getInt64Ty(), b_.getInt64Ty(), b_.getInt64Ty(), - b_.getInt8PtrTy()->getPointerTo(), b_.getInt32Ty(), - b_.getInt32Ty()->getPointerTo()}, + b_.getInt8PtrTy(), b_.getInt32Ty()}, /*isVarArg=*/false); auto* key_value_sort_func = llvm::cast( module_->getOrInsertFunction(fn_name, key_value_sort_type)); key_value_sort_func->setCallingConv(llvm::CallingConv::C); key_value_sort_func->setDoesNotThrow(); key_value_sort_func->setOnlyAccessesArgMemory(); - llvm::Value* values; - llvm::Value* sizes; - if (sort->values_count() == 0) { - values = llvm::Constant::getNullValue(b_.getInt8PtrTy()->getPointerTo()); - sizes = llvm::Constant::getNullValue(b_.getInt32Ty()->getPointerTo()); - } else { - values = llvm_ir::EmitAllocaAtFunctionEntryWithCount( - b_.getInt8PtrTy(), b_.getInt32(sort->values_count()), - "cc_values_alloca", &b_); - sizes = llvm_ir::EmitAllocaAtFunctionEntryWithCount( - b_.getInt32Ty(), b_.getInt32(sort->values_count()), "cc_sizes_alloca", - &b_); - for (int64 i = 0; i < sort->values_count(); ++i) { - llvm::Value* value_as_i8ptr = - PointerCast(destination_addresses[i + 1], b_.getInt8PtrTy()); - llvm::Value* slot_in_values_alloca = - ConstInBoundsGEP1_32(b_.getInt8PtrTy(), values, i); - Store(value_as_i8ptr, slot_in_values_alloca); - llvm::Value* slot_in_sizes_alloca = - ConstInBoundsGEP1_32(b_.getInt32Ty(), sizes, i); - llvm::Value* size = b_.getInt32(ShapeUtil::ByteSizeOfPrimitiveType( - sort->operand(i + 1)->shape().element_type())); - Store(size, slot_in_sizes_alloca); - } - } - Call(key_value_sort_func, - {PointerCast(destination_addresses[0], keys_native_type), + {PointerCast(keys_destination_address, keys_native_type), b_.getInt64(higher_dimensions), b_.getInt64(sort_dimension_elements), - b_.getInt64(lower_dimensions), values, - b_.getInt32(sort->values_count()), sizes}); - - if (sort->values_count() > 0) { - llvm_ir::EmitTuple(GetIrArrayFor(sort), destination_addresses, &b_, - module_); + b_.getInt64(lower_dimensions), + values != nullptr + ? PointerCast(values_destination_address, b_.getInt8PtrTy()) + : llvm::Constant::getNullValue(b_.getInt8PtrTy()), + b_.getInt32(values != nullptr ? ShapeUtil::ByteSizeOfPrimitiveType( + values->shape().element_type()) + : 0)}); + + if (values != nullptr) { + llvm_ir::EmitTuple(GetIrArrayFor(sort), + {keys_destination_address, values_destination_address}, + &b_, module_); } return Status::OK(); } diff --git a/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.cc b/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.cc index bbbb634f73..e0e7deb98e 100644 --- a/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.cc +++ b/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.cc @@ -99,9 +99,8 @@ void KeyValueSort(std::pair* row_to_sort, } template -void KeyValueSortImpl(KeyType* keys, int64 a, int64 b, int64 c, char** values, - int32 values_count, - int32* values_primitive_type_size_in_bytes) { +void KeyValueSortImpl(KeyType* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { // High-level idea of the iteration/sorting logic: // Conceptually we have a 3-dimensional shape [a, b, c]. b corresponds to the // dimension to sort, c is the product of the more minor dimensions (set to 1 @@ -130,7 +129,7 @@ void KeyValueSortImpl(KeyType* keys, int64 a, int64 b, int64 c, char** values, index % sort_dimension_offset + (index - index % sort_dimension_offset) * sort_dimension_elements; // TODO(b/26783907): We could define a custom iterator class that references - // all arrays. Then we could avoid the intermediate copy. However this + // both arrays. Then we could avoid the intermediate copy. However this // would become more complicated, and it is not clear if the benefit is high // enough. for (int64 i = 0; i < sort_dimension_elements; ++i) { @@ -141,109 +140,97 @@ void KeyValueSortImpl(KeyType* keys, int64 a, int64 b, int64 c, char** values, for (int64 i = 0; i < sort_dimension_elements; ++i) { keys[base_offset + i * sort_dimension_offset] = row_to_sort[i].first; } + if (values == nullptr) { + continue; + } // Reorder the values according to the order defined by the keys. - for (int32 idx = 0; idx < values_count; ++idx) { - for (int64 i = 0; i < sort_dimension_elements; ++i) { - int64 memory_index = - (base_offset + row_to_sort[i].second * sort_dimension_offset) * - values_primitive_type_size_in_bytes[idx]; - - reordered_values[i] = - std::string(values[idx] + memory_index, - values_primitive_type_size_in_bytes[idx]); - } - for (int64 i = 0; i < sort_dimension_elements; ++i) { - int64 memory_index = (base_offset + i * sort_dimension_offset) * - values_primitive_type_size_in_bytes[idx]; - memcpy(values[idx] + memory_index, reordered_values[i].c_str(), - values_primitive_type_size_in_bytes[idx]); - } + for (int64 i = 0; i < sort_dimension_elements; ++i) { + int64 memory_index = + (base_offset + row_to_sort[i].second * sort_dimension_offset) * + values_primitive_type_size_in_bytes; + + reordered_values[i] = std::string(values + memory_index, + values_primitive_type_size_in_bytes); + } + for (int64 i = 0; i < sort_dimension_elements; ++i) { + int64 memory_index = (base_offset + i * sort_dimension_offset) * + values_primitive_type_size_in_bytes; + memcpy(values + memory_index, reordered_values[i].c_str(), + values_primitive_type_size_in_bytes); } } } } // namespace TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortPRED( - bool* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + bool* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortS8( - int8* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + int8* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortU8( - uint8* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + uint8* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortS16( - int16* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + int16* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortU16( - uint16* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + uint16* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortF16( - Eigen::half* keys, int64 a, int64 b, int64 c, char** values, - int32 values_count, int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + Eigen::half* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortS32( - int32* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + int32* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortU32( - uint32* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + uint32* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortF32( - float* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + float* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortS64( - int64* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + int64* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortU64( - uint64* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + uint64* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } TF_ATTRIBUTE_NO_SANITIZE_MEMORY void __xla_cpu_runtime_KeyValueSortF64( - double* keys, int64 a, int64 b, int64 c, char** values, int32 values_count, - int32* values_primitive_type_size_in_bytes) { - KeyValueSortImpl(keys, a, b, c, values, values_count, - values_primitive_type_size_in_bytes); + double* keys, int64 a, int64 b, int64 c, char* values, + int32 values_primitive_type_size_in_bytes) { + KeyValueSortImpl(keys, a, b, c, values, values_primitive_type_size_in_bytes); } diff --git a/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.h b/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.h index 7821099386..28e35e82c1 100644 --- a/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.h +++ b/tensorflow/compiler/xla/service/cpu/runtime_key_value_sort.h @@ -22,75 +22,67 @@ limitations under the License. extern "C" { // 'keys' represents a 3-dimensional shape with dimensions [a, b, c]. The 'b' -// dimension of 'keys' is sorted into ascending order. If 'values_count' is <= -// 0, 'values' and 'values_primitive_type_size_in_bytes' can be nullptr. -// If 'values_count' > 0, they contain exactly 'values_count' many elements. -// Each element of 'values' also represents a 3-dimensional shape with -// dimensions [a, b, c], and the size of the primitive type of the i-th shape -// has exactly 'values_primitive_type_size_in_bytes[i]' bytes. The elements in -// each 'values' shape are reordered in such a way that if the element at index -// 'i' in 'keys' was moved to index 'j', the element at index 'i' in a 'values' -// shape is also moved to index 'j' (which means that the same elements -// correspond to each other as before). +// dimension of 'keys' is sorted into ascending order. 'values' can be nullptr. +// If 'values' is not nullptr, the elements in 'values' are reordered in such a +// way that if the element at index 'i' in 'keys' was moved to index 'j', the +// element at index 'i' in 'values' is also moved to index 'j' (which means that +// the same elements correspond to each other as before). extern void __xla_cpu_runtime_KeyValueSortPRED( bool* keys, tensorflow::int64 a, tensorflow::int64 b, tensorflow::int64 c, - char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + char* values, tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortS8( tensorflow::int8* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortU8( tensorflow::uint8* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortS16( tensorflow::int16* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortU16( tensorflow::uint16* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortF16( Eigen::half* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortS32( tensorflow::int32* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortU32( tensorflow::uint32* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortF32( float* keys, tensorflow::int64 a, tensorflow::int64 b, tensorflow::int64 c, - char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + char* values, tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortS64( tensorflow::int64* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortU64( tensorflow::uint64* keys, tensorflow::int64 a, tensorflow::int64 b, - tensorflow::int64 c, char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + tensorflow::int64 c, char* values, + tensorflow::int32 values_primitive_type_size_in_bytes); extern void __xla_cpu_runtime_KeyValueSortF64( double* keys, tensorflow::int64 a, tensorflow::int64 b, tensorflow::int64 c, - char** values, tensorflow::int32 values_count, - tensorflow::int32* values_primitive_type_size_in_bytes); + char* values, tensorflow::int32 values_primitive_type_size_in_bytes); } #endif // TENSORFLOW_COMPILER_XLA_SERVICE_CPU_RUNTIME_KEY_VALUE_SORT_H_ -- GitLab From cd1975be1ede20d30d7422c0d4e2f718e27bc766 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Tue, 16 Oct 2018 17:26:59 -0700 Subject: [PATCH 0181/1825] [TF:XLA] Merge XlaTransferManager and XlaDeviceContext. XlaTransferManager has no other users any more, so it serves no useful purpose. PiperOrigin-RevId: 217417415 --- tensorflow/compiler/jit/xla_device_context.cc | 63 +++++-------------- tensorflow/compiler/jit/xla_device_context.h | 37 ++--------- tensorflow/compiler/jit/xla_launch_util.cc | 2 +- 3 files changed, 21 insertions(+), 81 deletions(-) diff --git a/tensorflow/compiler/jit/xla_device_context.cc b/tensorflow/compiler/jit/xla_device_context.cc index af83c792e5..090021093d 100644 --- a/tensorflow/compiler/jit/xla_device_context.cc +++ b/tensorflow/compiler/jit/xla_device_context.cc @@ -50,7 +50,7 @@ void XlaDeviceAllocator::DeallocateRaw(void* ptr) { void XlaDeviceAllocator::GetStats(AllocatorStats* stats) { stats->Clear(); } -XlaTransferManager::XlaTransferManager( +XlaDeviceContext::XlaDeviceContext( std::shared_ptr compute_stream, std::shared_ptr host_to_device_stream, std::shared_ptr device_to_host_stream, xla::LocalClient* client, @@ -75,8 +75,8 @@ XlaTransferManager::XlaTransferManager( } } -Status XlaTransferManager::TransferLiteralToDevice( - const Tensor& host_tensor, Tensor* device_tensor) const { +Status XlaDeviceContext::TransferLiteralToDevice(const Tensor& host_tensor, + Tensor* device_tensor) const { xla::Shape xla_shape; TF_RETURN_IF_ERROR(TensorShapeToXLAShape(host_tensor.dtype(), host_tensor.shape(), &xla_shape)); @@ -112,7 +112,7 @@ Status XlaTransferManager::TransferLiteralToDevice( return Status::OK(); } -void XlaTransferManager::TransferLiteralFromDevice( +void XlaDeviceContext::TransferLiteralFromDevice( Tensor* host_tensor, const Tensor& device_tensor, const StatusCallback& done) const { xla::MutableBorrowingLiteral literal; @@ -134,10 +134,10 @@ void XlaTransferManager::TransferLiteralFromDevice( }); } -void XlaTransferManager::CopyCPUTensorToDevice(const Tensor* cpu_tensor, - Device* device, - Tensor* device_tensor, - StatusCallback done) const { +void XlaDeviceContext::CopyCPUTensorToDevice(const Tensor* cpu_tensor, + Device* device, + Tensor* device_tensor, + StatusCallback done) const { if (cpu_tensor->NumElements() == 0) { VLOG(2) << "CopyCPUTensorToDevice empty tensor"; done(Status::OK()); @@ -202,11 +202,10 @@ void XlaTransferManager::CopyCPUTensorToDevice(const Tensor* cpu_tensor, done(status); } -void XlaTransferManager::CopyDeviceTensorToCPU(const Tensor* device_tensor, - absl::string_view tensor_name, - Device* device, - Tensor* cpu_tensor, - StatusCallback done) { +void XlaDeviceContext::CopyDeviceTensorToCPU(const Tensor* device_tensor, + absl::string_view tensor_name, + Device* device, Tensor* cpu_tensor, + StatusCallback done) { if (device_tensor->NumElements() == 0) { VLOG(2) << "CopyDeviceTensorToCPU empty tensor"; done(Status::OK()); @@ -250,9 +249,9 @@ void XlaTransferManager::CopyDeviceTensorToCPU(const Tensor* device_tensor, done(status); } -void XlaTransferManager::CopyDeviceTensorToDevice(const Tensor& src_tensor, - Tensor* dst_tensor, - const StatusCallback& done) { +void XlaDeviceContext::CopyDeviceTensorToDevice(const Tensor& src_tensor, + Tensor* dst_tensor, + const StatusCallback& done) { VLOG(2) << "CopyDeviceTensorToDevice " << reinterpret_cast(src_tensor.tensor_data().data()) << " " @@ -320,36 +319,4 @@ void XlaTransferManager::CopyDeviceTensorToDevice(const Tensor& src_tensor, } } -XlaDeviceContext::XlaDeviceContext( - std::shared_ptr compute_stream, - std::shared_ptr host_to_device_stream, - std::shared_ptr device_to_host_stream, xla::LocalClient* client, - bool transfer_as_literal, - XlaCompiler::ShapeRepresentationFn shape_representation_fn, - thread::ThreadPool* thread_pool) - : manager_(std::move(compute_stream), std::move(host_to_device_stream), - std::move(device_to_host_stream), client, transfer_as_literal, - std::move(shape_representation_fn), thread_pool) {} - -void XlaDeviceContext::CopyCPUTensorToDevice(const Tensor* cpu_tensor, - Device* device, - Tensor* device_tensor, - StatusCallback done) const { - manager_.CopyCPUTensorToDevice(cpu_tensor, device, device_tensor, done); -} - -void XlaDeviceContext::CopyDeviceTensorToCPU(const Tensor* device_tensor, - absl::string_view tensor_name, - Device* device, Tensor* cpu_tensor, - StatusCallback done) { - manager_.CopyDeviceTensorToCPU(device_tensor, tensor_name, device, cpu_tensor, - done); -} - -void XlaDeviceContext::CopyDeviceTensorToDevice(const Tensor& src_tensor, - Tensor* dst_tensor, - const StatusCallback& done) { - manager_.CopyDeviceTensorToDevice(src_tensor, dst_tensor, done); -} - } // namespace tensorflow diff --git a/tensorflow/compiler/jit/xla_device_context.h b/tensorflow/compiler/jit/xla_device_context.h index df82421294..babb60acb5 100644 --- a/tensorflow/compiler/jit/xla_device_context.h +++ b/tensorflow/compiler/jit/xla_device_context.h @@ -44,9 +44,9 @@ class XlaDeviceAllocator : public Allocator { }; // Helper class for managing data transfers between host and XLA devices. -class XlaTransferManager { +class XlaDeviceContext : public DeviceContext { public: - explicit XlaTransferManager( + explicit XlaDeviceContext( std::shared_ptr compute_stream, std::shared_ptr host_to_device_stream, std::shared_ptr device_to_host_stream, @@ -55,10 +55,11 @@ class XlaTransferManager { thread::ThreadPool* thread_pool); void CopyCPUTensorToDevice(const Tensor* cpu_tensor, Device* device, - Tensor* device_tensor, StatusCallback done) const; + Tensor* device_tensor, + StatusCallback done) const override; void CopyDeviceTensorToCPU(const Tensor* device_tensor, absl::string_view tensor_name, Device* device, - Tensor* cpu_tensor, StatusCallback done); + Tensor* cpu_tensor, StatusCallback done) override; void CopyDeviceTensorToDevice(const Tensor& src_tensor, Tensor* dst_tensor, const StatusCallback& done); @@ -94,34 +95,6 @@ class XlaTransferManager { thread::ThreadPool* thread_pool_; }; -// DeviceContext for operators assigned to XlaDevice devices. The -// implementation must inherit from DeviceContext but otherwise just -// wraps the methods in XlaTransferManager. -class XlaDeviceContext : public DeviceContext { - public: - explicit XlaDeviceContext( - std::shared_ptr compute_stream, - std::shared_ptr host_to_device_stream, - std::shared_ptr device_to_host_stream, - xla::LocalClient* client, bool transfer_as_literal, - XlaCompiler::ShapeRepresentationFn shape_representation_fn, - thread::ThreadPool* thread_pool); - - void CopyCPUTensorToDevice(const Tensor* cpu_tensor, Device* device, - Tensor* device_tensor, - StatusCallback done) const override; - void CopyDeviceTensorToCPU(const Tensor* device_tensor, - absl::string_view tensor_name, Device* device, - Tensor* cpu_tensor, StatusCallback done) override; - void CopyDeviceTensorToDevice(const Tensor& src_tensor, Tensor* dst_tensor, - const StatusCallback& done); - - se::Stream* stream() const override { return manager_.stream(); } - - private: - XlaTransferManager manager_; -}; - } // namespace tensorflow #endif // TENSORFLOW_COMPILER_JIT_XLA_DEVICE_CONTEXT_H_ diff --git a/tensorflow/compiler/jit/xla_launch_util.cc b/tensorflow/compiler/jit/xla_launch_util.cc index 4f6fc4e068..0e8ee56ed8 100644 --- a/tensorflow/compiler/jit/xla_launch_util.cc +++ b/tensorflow/compiler/jit/xla_launch_util.cc @@ -239,7 +239,7 @@ Status XlaComputationLaunchContext::PopulateOutputs( // Copy host -> device. (Empty tensors don't have backing buffers.) // Manually allocate memory using an XlaTensorBuffer so we can allocate // as much memory as the device requires (as given by - // GetByteSizeRequirement). This avoids XlaTransferManager having to + // GetByteSizeRequirement). This avoids XlaDeviceContext having to // reallocate the device buffer later. VLOG(1) << "Constant output tensor on device"; -- GitLab From 3f7d60ca9d3f8037ba752220e80fc95d3c0be71a Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 17:56:15 -0700 Subject: [PATCH 0182/1825] Cleanup: Don't crash when querying node for non-existing attributes. PiperOrigin-RevId: 217420663 --- tensorflow/core/grappler/BUILD | 1 + .../optimizers/arithmetic_optimizer.cc | 33 +++++++------- .../grappler/optimizers/constant_folding.cc | 45 ++++++++++++------- tensorflow/core/grappler/utils.cc | 15 +++++++ tensorflow/core/grappler/utils.h | 13 +++++- tensorflow/core/grappler/utils_test.cc | 30 +++++++++++++ 6 files changed, 101 insertions(+), 36 deletions(-) diff --git a/tensorflow/core/grappler/BUILD b/tensorflow/core/grappler/BUILD index 7c6fe56e1f..3bad29a239 100644 --- a/tensorflow/core/grappler/BUILD +++ b/tensorflow/core/grappler/BUILD @@ -26,6 +26,7 @@ cc_library( "//tensorflow/core:lib", "//tensorflow/core:lib_internal", "//tensorflow/core:protos_all_cc", + "@com_google_absl//absl/types:span", ], ) diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 9b94d2706a..a09100f121 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -67,7 +67,8 @@ bool ValuesFromConstNode(const NodeDef& node, std::vector* values) { return false; } - if (node.attr().at("dtype").type() != DataTypeToEnum::value) { + if (node.attr().count("dtype") == 0 || node.attr().count("value") == 0 || + node.attr().at("dtype").type() != DataTypeToEnum::value) { return false; } @@ -158,14 +159,6 @@ void SetSourceDataType(DataType dtype, NodeDef* node) { SetDataTypeToAttr(dtype, SourceDataTypeAttrName(*node), node); } -Status CheckAttrExists(const NodeDef& node, const string& key) { - if (node.attr().count(key) == 0) { - return errors::InvalidArgument("Node '", node.name(), "'lacks '", key, - "' attr: ", node.DebugString()); - } - return Status::OK(); -} - NodeDef* GetTailOfValuePreservingChain( const NodeDef& node, const NodeMap& node_map, const std::unordered_set& nodes_to_preserve) { @@ -641,7 +634,7 @@ class AddOpsRewriteStage : public ArithmeticNodesGroupOptimizerStage { CHECK(!inputs.empty()) << "Inputs must be non-empty"; // Do not create redundant AddN nodes - if (inputs.size() == 1) { + if (inputs.size() == 1 || root_node.attr().count("T") == 0) { return inputs[0]; } @@ -1450,10 +1443,11 @@ class HoistCWiseUnaryChainsStage : public ArithmeticOptimizerStage { bool IsSupported(const NodeDef* node) const override { if (IsInPreserveSet(*node)) return false; - if (IsConcat(*node)) { + if (IsConcat(*node) && node->attr().count("N") != 0) { const int n = node->attr().at("N").i(); return n > 1; - } else if (IsSplit(*node) || IsSplitV(*node)) { + } else if ((IsSplit(*node) || IsSplitV(*node)) && + node->attr().count("num_split") != 0) { const int num_split = node->attr().at("num_split").i(); if (NumNonControlOutputs(*node, *ctx().node_map) > num_split) { // TODO(rmlarsen): Remove this constraint when we have optimizations @@ -1556,6 +1550,7 @@ class HoistCWiseUnaryChainsStage : public ArithmeticOptimizerStage { Status InitializeChains(const NodeDef& node, ChainLinkSet* tails) const { if (node_is_concat_) { // Handle concat nodes by looking backwards in the graph. + TF_RETURN_IF_ERROR(CheckAttrExists(node, "N")); const int n = node.attr().at("N").i(); const int start = node.op() == "Concat" ? 1 : 0; const int end = start + n; @@ -2029,6 +2024,8 @@ class FoldMultiplyIntoConv : public ArithmeticOptimizerStage { // Check that 'scale * weight' can be const folded. TF_RETURN_IF_TRUE(!IsConstant(*scale)); + TF_RETURN_IF_ERROR(CheckAttrsExist(*scale, {"dtype", "value"})); + TF_RETURN_IF_ERROR(CheckAttrExists(*weights, "dtype")); TF_RETURN_IF_TRUE(scale->attr().at("dtype").type() != weights->attr().at("dtype").type()); @@ -2803,6 +2800,7 @@ class UnaryOpsComposition : public ArithmeticOptimizerStage { } Status TrySimplify(NodeDef* root, string* simplified_node_name) override { + TF_RETURN_IF_ERROR(CheckAttrExists(*root, "T")); DataType dtype = root->attr().at("T").type(); // Keep a trace of all supported input nodes that can be fused together. @@ -3023,10 +3021,9 @@ class RemoveStackStridedSliceSameAxis : public ArithmeticOptimizerStage { const PartialTensorShape& pack_output_shape, int pack_axis, int* slice_start_value, bool* found) { *found = false; - for (auto key : {"begin_mask", "end_mask", "ellipsis_mask", "new_axis_mask", - "shrink_axis_mask"}) { - TF_RETURN_IF_ERROR(CheckAttrExists(*node, key)); - } + TF_RETURN_IF_ERROR( + CheckAttrsExist(*node, {"begin_mask", "end_mask", "ellipsis_mask", + "new_axis_mask", "shrink_axis_mask"})); const int begin_mask = node->attr().at("begin_mask").i(); const int end_mask = node->attr().at("end_mask").i(); @@ -3056,14 +3053,14 @@ class RemoveStackStridedSliceSameAxis : public ArithmeticOptimizerStage { Tensor slice_strides_t; TF_RETURN_IF_ERROR(CheckAttrExists(*slice_begin, "value")); - TF_RETURN_IF_ERROR(CheckAttrExists(*slice_end, "value")); - if (!slice_begin_t.FromProto(slice_begin->attr().at("value").tensor())) { return Status::OK(); } + TF_RETURN_IF_ERROR(CheckAttrExists(*slice_end, "value")); if (!slice_end_t.FromProto(slice_end->attr().at("value").tensor())) { return Status::OK(); } + TF_RETURN_IF_ERROR(CheckAttrExists(*slice_strides, "value")); if (!slice_strides_t.FromProto( slice_strides->attr().at("value").tensor())) { return Status::OK(); diff --git a/tensorflow/core/grappler/optimizers/constant_folding.cc b/tensorflow/core/grappler/optimizers/constant_folding.cc index c963f96858..8c56f665bf 100644 --- a/tensorflow/core/grappler/optimizers/constant_folding.cc +++ b/tensorflow/core/grappler/optimizers/constant_folding.cc @@ -349,6 +349,9 @@ Status ConstantFolding::MaterializeShapes(const GraphProperties& properties) { if (IsReallyConstant(*array_size)) { // Don't materialize 0 sizes to avoid triggering incorrect static // checks. A 0 sized array that can't grow isn't useful anyway. + if (array_size->attr().count("value") == 0) { + continue; + } const TensorProto& raw_val = array_size->attr().at("value").tensor(); if (raw_val.dtype() != DT_INT32) { continue; @@ -454,6 +457,9 @@ bool ExtractShape(const NodeDef& shape_node, const GraphProperties& properties, *min_id = std::min(*min_id, dim.size()); } } else { + if (shape_node.attr().count("value") == 0) { + return false; + } const TensorProto& raw_val = shape_node.attr().at("value").tensor(); if (raw_val.dtype() != DT_INT64 && raw_val.dtype() != DT_INT32) { return false; @@ -552,6 +558,7 @@ Status ConstantFolding::MaterializeBroadcastGradientArgs( reduce_dims[0] = bcast.grad_x_reduce_idx(); reduce_dims[1] = bcast.grad_y_reduce_idx(); + TF_RETURN_IF_ERROR(CheckAttrExists(node, "T")); const DataType type = node.attr().at("T").type(); NodeDef* out[2]; for (int j = 0; j < 2; ++j) { @@ -790,7 +797,8 @@ bool ConstantFolding::IsFoldable(const NodeDef& node) const { if (is_const) { // Don't fold strings constants for now since this causes problems with // checkpointing. - if (input_node->attr().at("dtype").type() == DT_STRING) { + if (input_node->attr().count("dtype") == 0 || + input_node->attr().at("dtype").type() == DT_STRING) { return false; } // Special case: If a Merge node has at least one constant input that @@ -985,6 +993,7 @@ Status ConstantFolding::EvaluateOneFoldable(const NodeDef& node, strings::StrCat("Can't fold ", node.name(), ", its ", input, " isn't constant")); } + TF_RETURN_IF_ERROR(CheckAttrExists(*input_node, "value")); const TensorProto& raw_val = input_node->attr().at("value").tensor(); Tensor* value = new Tensor(raw_val.dtype(), raw_val.tensor_shape()); CHECK(value->FromProto(raw_val)); @@ -1398,16 +1407,13 @@ bool ConstantFolding::IsOnes(const NodeDef& node) const { if (feed_nodes_.find(node.name()) != feed_nodes_.end()) { return false; } - if (node.op() == "OnesLike") { - return true; - } + if (node.op() == "OnesLike") return true; if (node.op() == "Fill") { NodeDef* values = node_map_->GetNode(NodeName(node.input(1))); return values != nullptr && IsOnes(*values); } - if (node.op() != "Const") { - return false; - } + if (node.op() != "Const") return false; + if (node.attr().count("dtype") == 0) return false; const auto dtype = node.attr().at("dtype").type(); switch (dtype) { IS_ONES_CASE(DT_BOOL); @@ -1434,16 +1440,13 @@ bool ConstantFolding::IsZeros(const NodeDef& node) const { if (feed_nodes_.find(node.name()) != feed_nodes_.end()) { return false; } - if (node.op() == "ZerosLike") { - return true; - } + if (node.op() == "ZerosLike") return true; if (node.op() == "Fill") { NodeDef* values = node_map_->GetNode(NodeName(node.input(1))); return values != nullptr && IsZeros(*values); } - if (!IsConstant(node)) { - return false; - } + if (!IsConstant(node)) return false; + if (node.attr().count("dtype") == 0) return false; const auto dtype = node.attr().at("dtype").type(); switch (dtype) { IS_ZEROS_CASE(DT_BOOL); @@ -1737,11 +1740,11 @@ Status ConstantFolding::SimplifyNode(bool use_shape_info, NodeDef* node, bool ConstantFolding::RemoveSplitOrSplitV(const GraphProperties& properties, GraphDef* optimized_graph, NodeDef* node) { + if (node->attr().count("num_split") == 0) return false; if (IsSplit(*node) && node->attr().at("num_split").i() == 1) { ReplaceOperationWithIdentity(1, properties, node, optimized_graph); return true; } - if (IsSplitV(*node) && node->attr().at("num_split").i() == 1) { ReplaceOperationWithIdentity(0, properties, node, optimized_graph); return true; @@ -1918,6 +1921,8 @@ Status ConstantFolding::SimplifyStridedSlice(const GraphProperties& properties, NodeDef* node, bool* success) { if (use_shape_info && IsStridedSlice(*node) && properties.GetInputProperties(node->name()).size() == 4) { + TF_RETURN_IF_ERROR( + CheckAttrsExist(*node, {"new_axis_mask", "shrink_axis_mask"})); if (node->attr().at("new_axis_mask").i() != 0 || node->attr().at("shrink_axis_mask").i() != 0) { // Skip nodes with new/shrink axis mask, since they involve dimension @@ -1952,6 +1957,8 @@ Status ConstantFolding::SimplifyStridedSlice(const GraphProperties& properties, return errors::InvalidArgument("Cannot parse tensor from proto: ", s.value().DebugString()); } + TF_RETURN_IF_ERROR( + CheckAttrsExist(*node, {"begin_mask", "end_mask", "ellipsis_mask"})); int begin_mask = node->attr().at("begin_mask").i(); int end_mask = node->attr().at("end_mask").i(); std::set expanded_ellipsis_indices; @@ -2280,7 +2287,7 @@ bool ConstantFolding::SimplifyReduction(const GraphProperties& properties, // Replace the reduction node with an identity node, that can be further // optimized by the model pruner. DataType output_type; - if (node->attr().count("T") > 0) { + if (node->attr().count("T") != 0) { output_type = node->attr().at("T").type(); } else { // This is an 'any' or 'all' reduction. The output is always boolean. @@ -2297,8 +2304,10 @@ bool ConstantFolding::SimplifyReduction(const GraphProperties& properties, bool ConstantFolding::SimplifyReshape(const GraphProperties& properties, bool use_shape_info, NodeDef* node) { - if (!use_shape_info) return false; - if (!IsSimplifiableReshape(*node, properties)) return false; + if (!use_shape_info || node->attr().count("T") == 0 || + !IsSimplifiableReshape(*node, properties)) { + return false; + } DataType output_type = node->attr().at("T").type(); node->set_op("Identity"); node->clear_attr(); @@ -2310,6 +2319,7 @@ bool ConstantFolding::SimplifyReshape(const GraphProperties& properties, Status ConstantFolding::SimplifyArithmeticOperations( const GraphProperties& properties, bool use_shape_info, GraphDef* optimized_graph, NodeDef* node, bool* success) { + *success = false; const bool is_mul = IsMul(*node) || IsLogicalAnd(*node); const bool is_matmul = IsMatMul(*node); const bool is_add = IsAdd(*node) || IsBiasAdd(*node) || IsLogicalOr(*node); @@ -2354,6 +2364,7 @@ Status ConstantFolding::SimplifyArithmeticOperations( // Replace 1 / y with Reciprocal op. if (y_matches_output_shape && is_any_div && x_is_one) { + TF_RETURN_IF_ERROR(CheckAttrExists(*node, "T")); DataType type = node->attr().at("T").type(); if (DataTypeIsFloating(type) || DataTypeIsComplex(type)) { ReplaceDivisionOfOnesByReciprocal(node, optimized_graph); diff --git a/tensorflow/core/grappler/utils.cc b/tensorflow/core/grappler/utils.cc index 5867d01324..e803e2ac71 100644 --- a/tensorflow/core/grappler/utils.cc +++ b/tensorflow/core/grappler/utils.cc @@ -547,5 +547,20 @@ Status SetTensorValue(DataType dtype, int value, Tensor* tensor) { #undef HANDLE_CASE +Status CheckAttrExists(const NodeDef& node, const string& key) { + if (node.attr().count(key) == 0) { + return errors::InvalidArgument("Node '", node.name(), "' lacks '", key, + "' attr: ", node.ShortDebugString()); + } + return Status::OK(); +} + +Status CheckAttrsExist(const NodeDef& node, absl::Span keys) { + for (const string& key : keys) { + TF_RETURN_IF_ERROR(CheckAttrExists(node, key)); + } + return Status::OK(); +} + } // end namespace grappler } // end namespace tensorflow diff --git a/tensorflow/core/grappler/utils.h b/tensorflow/core/grappler/utils.h index 0168ab1da3..39319eacb7 100644 --- a/tensorflow/core/grappler/utils.h +++ b/tensorflow/core/grappler/utils.h @@ -17,8 +17,12 @@ limitations under the License. #define TENSORFLOW_CORE_GRAPPLER_UTILS_H_ #include +#include +#include +#include +#include #include - +#include "absl/types/span.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/tensor.h" @@ -29,6 +33,7 @@ limitations under the License. #include "tensorflow/core/lib/gtl/flatmap.h" #include "tensorflow/core/lib/gtl/flatset.h" #include "tensorflow/core/lib/gtl/inlined_vector.h" +#include "tensorflow/core/platform/types.h" namespace tensorflow { namespace grappler { @@ -244,6 +249,12 @@ int NumNonControlDataOutputs(const NodeDef& node, const NodeMap& node_map); // Removes redundant control inputs from node. void DedupControlInputs(NodeDef* node); +// Returns an error if an attribute with the given key does not exist in node. +Status CheckAttrExists(const NodeDef& node, const string& key); + +// Returns an error if attributes with the given keys do not exist in node. +Status CheckAttrsExist(const NodeDef& node, absl::Span keys); + // Returns the data type in attribute `attr_name` of `node`. If that attribute // doesn't exist, returns DT_INVALID. DataType GetDataTypeFromAttr(const NodeDef& node, const string& attr_name); diff --git a/tensorflow/core/grappler/utils_test.cc b/tensorflow/core/grappler/utils_test.cc index 9b6c1f690b..447195b001 100644 --- a/tensorflow/core/grappler/utils_test.cc +++ b/tensorflow/core/grappler/utils_test.cc @@ -14,6 +14,9 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/grappler/utils.h" + +#include +#include #include "tensorflow/cc/ops/standard_ops.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/grappler/grappler_item.h" @@ -24,6 +27,7 @@ limitations under the License. #include "tensorflow/core/platform/notification.h" #include "tensorflow/core/platform/test.h" #include "tensorflow/core/platform/test_benchmark.h" +#include "tensorflow/core/platform/types.h" namespace tensorflow { namespace grappler { @@ -350,6 +354,32 @@ TEST_F(UtilsTest, NumNonControlOutputs) { EXPECT_EQ(1, NumNonControlDataOutputs(*add_node, node_map)); } +TEST(CheckAttrExists, All) { + NodeDef node; + node.set_name("node"); + (*node.mutable_attr())["apple"].set_i(7); + (*node.mutable_attr())["pear"].set_b(true); + + TF_EXPECT_OK(CheckAttrExists(node, "apple")); + TF_EXPECT_OK(CheckAttrExists(node, "pear")); + + TF_EXPECT_OK(CheckAttrsExist(node, {})); + TF_EXPECT_OK(CheckAttrsExist(node, {"apple"})); + TF_EXPECT_OK(CheckAttrsExist(node, {"pear"})); + TF_EXPECT_OK(CheckAttrsExist(node, {"apple", "pear"})); + TF_EXPECT_OK(CheckAttrsExist(node, {"pear", "apple"})); + + Status status = CheckAttrExists(node, "banana"); + EXPECT_FALSE(status.ok()); + EXPECT_EQ(status.ToString(), + "Invalid argument: Node 'node' lacks 'banana' attr: name: \"node\" " + "attr { key: \"apple\" value { i: 7 } } attr { key: \"pear\" value " + "{ b: true } }"); + EXPECT_FALSE(CheckAttrsExist(node, {""}).ok()); + EXPECT_FALSE(CheckAttrsExist(node, {"pear", "cherry"}).ok()); + EXPECT_FALSE(CheckAttrsExist(node, {"banana", "apple"}).ok()); +} + TEST_F(UtilsTest, DeleteNodes) { // TODO(rmlarsen): write forgotten test. } -- GitLab From b306ad9846238b7a396694c07510a1fc161627b2 Mon Sep 17 00:00:00 2001 From: Alexey Radul Date: Tue, 16 Oct 2018 17:57:09 -0700 Subject: [PATCH 0183/1825] Extract nested functions functions in anf_test.py that include `exec` to toplevel, for a baroque compatibility reason. PiperOrigin-RevId: 217420773 --- .../pyct/common_transformers/anf_test.py | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/tensorflow/python/autograph/pyct/common_transformers/anf_test.py b/tensorflow/python/autograph/pyct/common_transformers/anf_test.py index ccc7e4ca8f..525d4886de 100644 --- a/tensorflow/python/autograph/pyct/common_transformers/anf_test.py +++ b/tensorflow/python/autograph/pyct/common_transformers/anf_test.py @@ -43,6 +43,29 @@ class DummyGensym(object): return stem + '_' + str(1000 + self._idx) +# These two test functions have to be top-level, not nested, for compatibility +# with some unknown version of Python 2.7 preceding 2.7.15. Why? Because +# `exec` and nested function definitions _incomaptibly_ change the +# representation of local variables, such that `exec` inside a nested function +# definition is a syntax error in that version. The tuple form of `exec` fixes +# this problem, but apparently that was introduced in some unknown version of +# Python that's more recent than at least one version that we wish to be +# compatible with. +def exec_test_function(): + # The point is to test A-normal form conversion of exec + # pylint: disable=exec-used + exec('computed' + 5 + 'stuff', globals(), locals()) + + +def exec_expected_result(): + # pylint: disable=exec-used + tmp_1001 = 'computed' + 5 + tmp_1002 = tmp_1001 + 'stuff' + tmp_1003 = globals() + tmp_1004 = locals() + exec(tmp_1002, tmp_1003, tmp_1004) + + class AnfTransformerTest(test.TestCase): def _simple_source_info(self): @@ -357,21 +380,7 @@ class AnfTransformerTest(test.TestCase): self.assert_body_anfs_as_expected(expected_result, test_function) def test_exec(self): - - def test_function(): - # The point is to test A-normal form conversion of exec - # pylint: disable=exec-used - exec('computed' + 5 + 'stuff', globals(), locals()) - - def expected_result(): - # pylint: disable=exec-used - tmp_1001 = 'computed' + 5 - tmp_1002 = tmp_1001 + 'stuff' - tmp_1003 = globals() - tmp_1004 = locals() - exec(tmp_1002, tmp_1003, tmp_1004) - - self.assert_body_anfs_as_expected(expected_result, test_function) + self.assert_body_anfs_as_expected(exec_expected_result, exec_test_function) def test_simple_while_and_assert(self): -- GitLab From bddb651ed737f937d16dc93828469cf3abe331b2 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 18:03:57 -0700 Subject: [PATCH 0184/1825] Move from deprecated self.test_session() to self.session() or self.cached_session(). Move to cached_session() if the session is create more than once per test. Move to session() otherwise. self.test_session() has been deprecated in 9962eb5e84b15e309410071b06c2ed2d6148ed44 as its name confuses readers of the test. Moving to session() instead which slightly changes the semantic of the function: * the session is not cached anymore (a new session is created). * the session is closed when exiting the "with" scope. PiperOrigin-RevId: 217421579 --- .../python/kernel_tests/accumulate_n_test.py | 8 +- .../python/kernel_tests/aggregate_ops_test.py | 6 +- .../python/kernel_tests/argmax_op_test.py | 6 +- .../python/kernel_tests/array_ops_test.py | 58 ++++++------ .../python/kernel_tests/atrous_conv2d_test.py | 10 +- .../python/kernel_tests/basic_gpu_test.py | 12 +-- .../kernel_tests/batch_gather_op_test.py | 10 +- .../kernel_tests/batch_matmul_op_test.py | 4 +- .../kernel_tests/batch_scatter_ops_test.py | 6 +- .../python/kernel_tests/bias_op_test.py | 6 +- .../python/kernel_tests/bincount_op_test.py | 12 +-- .../python/kernel_tests/bitcast_op_test.py | 2 +- .../kernel_tests/broadcast_to_ops_test.py | 12 +-- .../python/kernel_tests/bucketize_op_test.py | 8 +- .../python/kernel_tests/cast_op_test.py | 6 +- .../python/kernel_tests/cholesky_op_test.py | 10 +- .../python/kernel_tests/clip_ops_test.py | 52 +++++------ .../compare_and_bitpack_op_test.py | 2 +- .../python/kernel_tests/concat_op_test.py | 36 ++++---- .../python/kernel_tests/cond_v2_test.py | 6 +- .../python/kernel_tests/constant_op_test.py | 22 ++--- .../kernel_tests/control_flow_ops_py_test.py | 26 +++--- tensorflow/python/kernel_tests/conv1d_test.py | 2 +- .../conv2d_backprop_filter_grad_test.py | 2 +- .../kernel_tests/conv2d_transpose_test.py | 6 +- .../python/kernel_tests/conv_ops_3d_test.py | 8 +- .../python/kernel_tests/conv_ops_test.py | 10 +- .../kernel_tests/ctc_decoder_ops_test.py | 2 +- .../python/kernel_tests/ctc_loss_op_test.py | 8 +- .../python/kernel_tests/dct_ops_test.py | 2 +- .../kernel_tests/decode_image_op_test.py | 8 +- .../python/kernel_tests/denormal_test.py | 2 +- .../kernel_tests/dense_update_ops_test.py | 6 +- .../kernel_tests/depthtospace_op_test.py | 12 +-- .../kernel_tests/depthwise_conv_op_test.py | 12 +-- .../kernel_tests/determinant_op_test.py | 4 +- .../python/kernel_tests/diag_op_test.py | 44 ++++----- .../kernel_tests/draw_bounding_box_op_test.py | 2 +- .../kernel_tests/dynamic_partition_op_test.py | 26 +++--- .../kernel_tests/dynamic_stitch_op_test.py | 18 ++-- .../kernel_tests/edit_distance_op_test.py | 4 +- .../python/kernel_tests/embedding_ops_test.py | 18 ++-- .../extract_image_patches_op_test.py | 2 +- .../extract_volume_patches_op_test.py | 2 +- .../python/kernel_tests/fft_ops_test.py | 14 +-- .../python/kernel_tests/fifo_queue_test.py | 2 +- .../python/kernel_tests/gather_nd_op_test.py | 40 ++++---- .../python/kernel_tests/gather_op_test.py | 16 ++-- .../python/kernel_tests/init_ops_test.py | 45 +++++---- .../python/kernel_tests/inplace_ops_test.py | 18 ++-- .../kernel_tests/large_concat_op_test.py | 2 +- .../python/kernel_tests/linalg_grad_test.py | 4 +- tensorflow/python/kernel_tests/losses_test.py | 6 +- tensorflow/python/kernel_tests/lrn_op_test.py | 6 +- .../python/kernel_tests/map_stage_op_test.py | 24 ++--- .../python/kernel_tests/matmul_op_test.py | 4 +- .../kernel_tests/matrix_band_part_op_test.py | 4 +- .../matrix_exponential_op_test.py | 6 +- .../kernel_tests/matrix_inverse_op_test.py | 4 +- .../kernel_tests/matrix_logarithm_op_test.py | 4 +- .../kernel_tests/matrix_solve_ls_op_test.py | 6 +- .../kernel_tests/matrix_solve_op_test.py | 10 +- .../matrix_triangular_solve_op_test.py | 2 +- .../kernel_tests/morphological_ops_test.py | 8 +- .../neon_depthwise_conv_op_test.py | 4 +- .../python/kernel_tests/norm_op_test.py | 2 +- .../kernel_tests/nth_element_op_test.py | 10 +- .../python/kernel_tests/numerics_test.py | 6 +- .../python/kernel_tests/one_hot_op_test.py | 2 +- tensorflow/python/kernel_tests/pad_op_test.py | 30 +++--- .../parameterized_truncated_normal_op_test.py | 6 +- .../partitioned_variables_test.py | 4 +- tensorflow/python/kernel_tests/pool_test.py | 16 ++-- .../kernel_tests/pooling_ops_3d_test.py | 4 +- .../python/kernel_tests/pooling_ops_test.py | 32 +++---- .../python/kernel_tests/py_func_test.py | 2 +- tensorflow/python/kernel_tests/qr_op_test.py | 6 +- .../python/kernel_tests/reader_ops_test.py | 2 +- .../python/kernel_tests/reduction_ops_test.py | 42 ++++----- .../python/kernel_tests/relu_op_test.py | 14 +-- .../python/kernel_tests/reshape_op_test.py | 2 +- .../kernel_tests/reverse_sequence_op_test.py | 2 +- tensorflow/python/kernel_tests/rnn_test.py | 4 +- .../python/kernel_tests/scan_ops_test.py | 16 ++-- .../kernel_tests/scatter_nd_ops_test.py | 16 ++-- .../python/kernel_tests/scatter_ops_test.py | 8 +- .../segment_reduction_ops_test.py | 76 +++++++-------- .../kernel_tests/self_adjoint_eig_op_test.py | 8 +- .../python/kernel_tests/shape_ops_test.py | 42 ++++----- .../python/kernel_tests/slice_op_test.py | 28 +++--- .../python/kernel_tests/softmax_op_test.py | 6 +- .../python/kernel_tests/softplus_op_test.py | 2 +- .../python/kernel_tests/softsign_op_test.py | 2 +- .../kernel_tests/spacetobatch_op_test.py | 8 +- .../kernel_tests/spacetodepth_op_test.py | 12 +-- .../python/kernel_tests/sparse_add_op_test.py | 14 +-- .../kernel_tests/sparse_concat_op_test.py | 20 ++-- .../kernel_tests/sparse_matmul_op_test.py | 2 +- .../python/kernel_tests/sparse_ops_test.py | 86 ++++++++--------- .../kernel_tests/sparse_reorder_op_test.py | 10 +- .../kernel_tests/sparse_reshape_op_test.py | 32 +++---- .../sparse_serialization_ops_test.py | 24 ++--- .../kernel_tests/sparse_slice_op_test.py | 14 +-- .../kernel_tests/sparse_split_op_test.py | 14 +-- .../sparse_tensor_dense_matmul_grad_test.py | 2 +- .../sparse_tensor_dense_matmul_op_test.py | 6 +- .../sparse_tensors_map_ops_test.py | 8 +- .../sparse_to_dense_op_py_test.py | 18 ++-- .../kernel_tests/sparse_xent_op_test.py | 22 ++--- .../python/kernel_tests/split_op_test.py | 14 +-- .../python/kernel_tests/stack_op_test.py | 36 ++++---- .../python/kernel_tests/stack_ops_test.py | 28 +++--- .../python/kernel_tests/stage_op_test.py | 14 +-- .../kernel_tests/string_length_op_test.py | 4 +- tensorflow/python/kernel_tests/svd_op_test.py | 6 +- .../kernel_tests/tensor_array_ops_test.py | 92 +++++++++---------- .../python/kernel_tests/tensordot_op_test.py | 4 +- .../python/kernel_tests/topk_op_test.py | 6 +- .../python/kernel_tests/trace_op_test.py | 2 +- .../python/kernel_tests/transpose_op_test.py | 20 ++-- .../python/kernel_tests/unstack_op_test.py | 8 +- .../python/kernel_tests/variables_test.py | 2 +- .../python/kernel_tests/where_op_test.py | 8 +- .../python/kernel_tests/xent_op_test.py | 10 +- .../python/kernel_tests/zero_division_test.py | 2 +- 125 files changed, 843 insertions(+), 844 deletions(-) diff --git a/tensorflow/python/kernel_tests/accumulate_n_test.py b/tensorflow/python/kernel_tests/accumulate_n_test.py index 0bc5268f38..7889edc198 100644 --- a/tensorflow/python/kernel_tests/accumulate_n_test.py +++ b/tensorflow/python/kernel_tests/accumulate_n_test.py @@ -36,7 +36,7 @@ class AccumulateNV2Test(test_util.TensorFlowTestCase): np.random.seed(12345) x = [np.random.random((1, 2, 3, 4, 5)) - 0.5 for _ in range(5)] tf_x = ops.convert_n_to_tensor(x) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllClose(sum(x), math_ops.accumulate_n(tf_x).eval()) self.assertAllClose(x[0] * 5, math_ops.accumulate_n([tf_x[0]] * 5).eval()) @@ -45,13 +45,13 @@ class AccumulateNV2Test(test_util.TensorFlowTestCase): np.random.seed(54321) x = [np.random.randint(-128, 128, (5, 4, 3, 2, 1)) for _ in range(6)] tf_x = ops.convert_n_to_tensor(x) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual(sum(x), math_ops.accumulate_n(tf_x).eval()) self.assertAllEqual(x[0] * 6, math_ops.accumulate_n([tf_x[0]] * 6).eval()) def testUnknownShape(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x0 = array_ops.placeholder(dtype=dtypes_lib.int32, shape=[None]) acc = math_ops.accumulate_n([x0, x0], shape=[None]) self.assertAllEqual([2, 4], acc.eval(feed_dict={x0: [1, 2]})) @@ -59,7 +59,7 @@ class AccumulateNV2Test(test_util.TensorFlowTestCase): def testGrad(self): np.random.seed(42) for num_inputs in range(1, 10): - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: input_vars = [ variables.Variable(10.0 * np.random.random()) for _ in range(0, num_inputs) diff --git a/tensorflow/python/kernel_tests/aggregate_ops_test.py b/tensorflow/python/kernel_tests/aggregate_ops_test.py index 72dff6b3da..0f15319cb5 100644 --- a/tensorflow/python/kernel_tests/aggregate_ops_test.py +++ b/tensorflow/python/kernel_tests/aggregate_ops_test.py @@ -57,7 +57,7 @@ class AddNTest(test.TestCase): def testAddN(self): np.random.seed(12345) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: for dtype in self._supported_types(): for count in range(1, self._MAX_N + 1): data = [self._buildData((2, 2), dtype) for _ in range(count)] @@ -69,7 +69,7 @@ class AddNTest(test.TestCase): def testUnknownShapes(self): np.random.seed(12345) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: for dtype in self._supported_types(): data = self._buildData((2, 2), dtype) for count in range(1, self._MAX_N + 1): @@ -96,7 +96,7 @@ class AddNTest(test.TestCase): # TODO(ebrevdo): Re-enable use_gpu=True once non-DMA Variant # copying between CPU and GPU is supported. - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): variant_const_3 = create_constant_variant(3) variant_const_4 = create_constant_variant(4) variant_const_5 = create_constant_variant(5) diff --git a/tensorflow/python/kernel_tests/argmax_op_test.py b/tensorflow/python/kernel_tests/argmax_op_test.py index 127d14c250..fa370c17b4 100644 --- a/tensorflow/python/kernel_tests/argmax_op_test.py +++ b/tensorflow/python/kernel_tests/argmax_op_test.py @@ -34,7 +34,7 @@ class ArgMaxTest(test.TestCase): expected_values, use_gpu=False, expected_err_re=None): - with self.test_session(use_gpu=use_gpu): + with self.session(use_gpu=use_gpu): ans = method(x, axis=axis) if expected_err_re is None: tf_ans = ans.eval() @@ -77,7 +77,7 @@ class ArgMaxTest(test.TestCase): def testFloatInt32Output(self): x = np.asarray(100 * np.random.randn(200), dtype=np.float32) expected_values = x.argmax() - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ans = math_ops.argmax(x, axis=0, output_type=dtypes.int32) tf_ans = ans.eval() self.assertEqual(np.int32, tf_ans.dtype) @@ -85,7 +85,7 @@ class ArgMaxTest(test.TestCase): # the values don't have a range that exceeds 32-bit integers. self.assertAllEqual(tf_ans, expected_values) expected_values = x.argmin() - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ans = math_ops.argmin(x, axis=0, output_type=dtypes.int32) tf_ans = ans.eval() self.assertEqual(np.int32, tf_ans.dtype) diff --git a/tensorflow/python/kernel_tests/array_ops_test.py b/tensorflow/python/kernel_tests/array_ops_test.py index dcc594789e..78fc091cf8 100644 --- a/tensorflow/python/kernel_tests/array_ops_test.py +++ b/tensorflow/python/kernel_tests/array_ops_test.py @@ -283,7 +283,7 @@ class ReverseV2Test(test_util.TensorFlowTestCase): def testReverse0DimAuto(self): x_np = 4 for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): x_tf = array_ops.reverse_v2(x_np, []).eval() self.assertAllEqual(x_tf, x_np) @@ -292,7 +292,7 @@ class ReverseV2Test(test_util.TensorFlowTestCase): for use_gpu in [False, True]: for axis_dtype in [dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): x_tf = array_ops.reverse_v2(x_np, constant_op.constant( [0], dtype=axis_dtype)).eval() @@ -304,7 +304,7 @@ class ReverseV2Test(test_util.TensorFlowTestCase): for reverse_f in [array_ops.reverse_v2, array_ops.reverse]: for use_gpu in [False, True]: for axis_dtype in [dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): x_tf_1 = reverse_f(x_np, constant_op.constant( [0], dtype=axis_dtype)).eval() x_tf_2 = reverse_f(x_np, constant_op.constant( @@ -391,7 +391,7 @@ class ReverseV2Test(test_util.TensorFlowTestCase): def testReverseRowsOf3Channels(self): """Tests optimized code for reversing rows with last dim size = 3.""" - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for reverse_f in [array_ops.reverse_v2, array_ops.reverse]: for outer_size in (1, 2): for middle_size in list(range(50)) + [100000]: @@ -403,7 +403,7 @@ class ReverseV2Test(test_util.TensorFlowTestCase): self.assertAllEqual(x_tf, np_answer) def testReverseRowsOf4Channels(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for reverse_f in [array_ops.reverse_v2, array_ops.reverse]: for outer_size in (1, 2): for middle_size in list(range(50)) + [100000]: @@ -415,7 +415,7 @@ class ReverseV2Test(test_util.TensorFlowTestCase): self.assertAllEqual(x_tf, np_answer) def testReverseColumnsOf3Channels(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for reverse_f in [array_ops.reverse_v2, array_ops.reverse]: for outer_size in list(range(50)) + [100000]: for middle_size in (1, 2): @@ -433,7 +433,7 @@ class MeshgridTest(test_util.TensorFlowTestCase): for index in ("ij", "xy"): numpy_out = np.meshgrid(x, y, indexing=index) tf_out = array_ops.meshgrid(x, y, indexing=index) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): for xx, yy in zip(numpy_out, tf_out): self.assertAllEqual(xx, yy.eval()) @@ -446,7 +446,7 @@ class MeshgridTest(test_util.TensorFlowTestCase): x += 1j inputs.append(x) numpy_out = np.meshgrid(*inputs, indexing=index) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_out = array_ops.meshgrid(*inputs, indexing=index) for x_np, x_tf in zip(numpy_out, tf_out): self.assertAllEqual(x_np, x_tf.eval()) @@ -523,7 +523,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): def test_basic_slice(self): for tensor_type in STRIDED_SLICE_TYPES: - with self.test_session(use_gpu=not tensor_type.is_integer): + with self.cached_session(use_gpu=not tensor_type.is_integer): checker = StridedSliceChecker( self, StridedSliceChecker.REF_TENSOR, tensor_type=tensor_type) _ = checker[:, :, :] @@ -551,7 +551,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): def testInt64GPU(self): if not test_util.is_gpu_available(): self.skipTest("No GPU available") - with self.test_session(use_gpu=True, force_gpu=True): + with self.session(use_gpu=True, force_gpu=True): x = constant_op.constant([1., 2., 3.]) begin = constant_op.constant([2], dtype=dtypes.int64) end = constant_op.constant([3], dtype=dtypes.int64) @@ -576,7 +576,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): v[0] # pylint: disable=pointless-statement def testDegenerateSlices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): checker = StridedSliceChecker(self, StridedSliceChecker.REF_TENSOR) # degenerate by offering a forward interval with a negative stride _ = checker[0:-1:-1, :, :] @@ -586,7 +586,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): _ = checker[-1:0, 2:2, 2:3:-1] def testEllipsis(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): raw = [[[[[1, 2], [3, 4], [5, 6]]], [[[7, 8], [9, 10], [11, 12]]]]] checker = StridedSliceChecker(self, raw) @@ -606,7 +606,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): _ = checker[..., :, ...].eval() def testShrink(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): raw = [[[[[1, 2, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]], [[[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]]] checker = StridedSliceChecker(self, raw) @@ -616,7 +616,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): _ = checker[:, :, 0] def testBothNewAxisAndShrink(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ones = array_ops.placeholder(shape=[2, 2], dtype=dtypes.int16) self.assertAllEqual( ones[array_ops.newaxis, :, 0].eval( @@ -624,7 +624,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): [[1, 1]]) def testTensorIndexing(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): raw = [[[[[1, 2, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]], [[[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]]] checker = StridedSliceChecker(self, raw, check_type_infer=False) @@ -640,7 +640,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): _ = checker[..., 3] def testExpand(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): raw = [[[[[1, 2, 4, 5], [5, 6, 7, 8], [9, 10, 11, 12]]], [[[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]]] checker = StridedSliceChecker(self, raw) @@ -657,7 +657,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): _ = checker[np.newaxis, ..., np.newaxis] def testExpandVariable(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = variables.Variable(7, dtype=dtypes.int32) x.initializer.run() y = x[None].eval() @@ -665,7 +665,7 @@ class StridedSliceTest(test_util.TensorFlowTestCase): self.assertAllEqual(y, (7,)) def testOptimizedCases(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): checker = StridedSliceChecker(self, StridedSliceChecker.REF_TENSOR_ALIGNED) # Identity @@ -694,7 +694,7 @@ class StridedSliceShapeTest(test_util.TensorFlowTestCase): """Test the shape inference of StridedSliceShapes.""" def testUnknown(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): uncertain_tensor = array_ops.placeholder(dtypes.float32) a = StridedSliceShapeChecker(uncertain_tensor) a_slice_shape = a[...] @@ -705,7 +705,7 @@ class StridedSliceShapeTest(test_util.TensorFlowTestCase): self.assertEqual(x.as_list(), y.as_list()) def testTensorShapeUncertain(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): uncertain_tensor = array_ops.placeholder( dtypes.float32, shape=(5, None, 7)) a = StridedSliceShapeChecker(uncertain_tensor) @@ -728,7 +728,7 @@ class StridedSliceShapeTest(test_util.TensorFlowTestCase): tensor_shape.TensorShape([5, None, 1, 4])) def testTensorValuedIndexShape(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): defined_shape_tensor = array_ops.placeholder( dtypes.float32, shape=(5, 3, 7)) index_value = array_ops.placeholder(dtypes.int32, shape=()) @@ -784,7 +784,7 @@ class StridedSliceGradTest(test_util.TensorFlowTestCase): """Test that strided slice's custom gradient produces correct gradients.""" def testGradient(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: var = variables.Variable( array_ops.reshape( math_ops.range(1, 97, 1, dtype=dtypes.float32), shape=(6, 4, 4))) @@ -805,7 +805,7 @@ class StridedSliceGradTest(test_util.TensorFlowTestCase): _ = grad[:, 200, :] def testGradientZero(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: var = variables.Variable(8.) init = variables.global_variables_initializer() sess.run(init) @@ -813,7 +813,7 @@ class StridedSliceGradTest(test_util.TensorFlowTestCase): _ = grad[tuple()] def testInt64Indices(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: a = math_ops.range(3, dtype=dtypes.float32) index = constant_op.constant(1, dtype=dtypes.int64) b = 2. * a[index] @@ -825,7 +825,7 @@ class StridedSliceGradTypeTest(test_util.TensorFlowTestCase): """Test varied index types and host located memory.""" def testHostVsDevice(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: var2 = variables.Variable( array_ops.reshape( math_ops.cast(math_ops.range(1, 5, 1), dtypes.float32), @@ -839,7 +839,7 @@ class StridedSliceGradTypeTest(test_util.TensorFlowTestCase): sess.run(foo) def testInt64Shape(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: original_dy = array_ops.reshape( math_ops.cast(math_ops.range(1, 5, 1), dtypes.float32), shape=(4, 1, 1)) @@ -853,7 +853,7 @@ class StridedSliceGradTypeTest(test_util.TensorFlowTestCase): sess.run(dx) def testMixedIndexTypes(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: original_dy = array_ops.reshape( math_ops.cast(math_ops.range(1, 5, 1), dtypes.float32), shape=(4, 1, 1)) @@ -1212,7 +1212,7 @@ class InvertPermutationTest(test_util.TensorFlowTestCase): def testInvertPermutation(self): for dtype in [dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = constant_op.constant([3, 4, 0, 2, 1], dtype=dtype) y = array_ops.invert_permutation(x) self.assertAllEqual(y.get_shape(), [5]) @@ -1278,7 +1278,7 @@ class SnapshotOpTest(test_util.TensorFlowTestCase): def testInvertPermutation(self): for dtype in [dtypes.int32, dtypes.int64, dtypes.float32, dtypes.float64]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = constant_op.constant([0, 1, 2, 3], dtype=dtype) y = gen_array_ops.snapshot(x) self.assertAllEqual(y.eval(), [0, 1, 2, 3]) diff --git a/tensorflow/python/kernel_tests/atrous_conv2d_test.py b/tensorflow/python/kernel_tests/atrous_conv2d_test.py index ab1d698f6e..1d82b3d058 100644 --- a/tensorflow/python/kernel_tests/atrous_conv2d_test.py +++ b/tensorflow/python/kernel_tests/atrous_conv2d_test.py @@ -59,7 +59,7 @@ def _upsample_filters(filters, rate): class AtrousConv2DTest(test.TestCase): def testAtrousConv2DForward(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # Input: [batch, height, width, input_depth] height = 9 for width in [9, 10]: # Test both odd and even width. @@ -105,7 +105,7 @@ class AtrousConv2DTest(test.TestCase): padding = "SAME" # The padding needs to be "SAME" np.random.seed(1) # Make it reproducible. - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # Input: [batch, height, width, input_depth] for height in range(15, 17): for width in range(15, 17): @@ -134,7 +134,7 @@ class AtrousConv2DTest(test.TestCase): self.assertAllClose(y1.eval(), y2.eval(), rtol=1e-2, atol=1e-2) def testGradient(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # Input: [batch, height, width, input_depth] x_shape = [2, 5, 6, 2] # Filter: [kernel_height, kernel_width, input_depth, output_depth] @@ -161,7 +161,7 @@ class AtrousConv2DTest(test.TestCase): class AtrousConv2DTransposeTest(test.TestCase): def testAtrousConv2DTransposeForward(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # Input: [batch, height, width, input_depth] height = 9 for width in [9, 10]: # Test both odd and even width. @@ -200,7 +200,7 @@ class AtrousDepthwiseConv2DTest(test.TestCase): def testAtrousDepthwiseConv2DForward(self): strides = [1, 1, 1, 1] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # Input: [batch, height, width, input_depth] height = 9 for width in [9, 10]: # Test both odd and even width. diff --git a/tensorflow/python/kernel_tests/basic_gpu_test.py b/tensorflow/python/kernel_tests/basic_gpu_test.py index 67e8618198..225c1b35ae 100644 --- a/tensorflow/python/kernel_tests/basic_gpu_test.py +++ b/tensorflow/python/kernel_tests/basic_gpu_test.py @@ -40,13 +40,13 @@ from tensorflow.python.platform import test class GPUBinaryOpsTest(test.TestCase): def _compareGPU(self, x, y, np_func, tf_func): - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: inx = ops.convert_to_tensor(x) iny = ops.convert_to_tensor(y) out = tf_func(inx, iny) tf_gpu = sess.run(out) - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: inx = ops.convert_to_tensor(x) iny = ops.convert_to_tensor(y) out = tf_func(inx, iny) @@ -93,7 +93,7 @@ class MathBuiltinUnaryTest(test.TestCase): def _compare(self, x, np_func, tf_func, use_gpu): np_out = np_func(x) - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: inx = ops.convert_to_tensor(x) ofunc = tf_func(inx) tf_out = sess.run(ofunc) @@ -143,7 +143,7 @@ class MathBuiltinUnaryTest(test.TestCase): np_out = np.floor_divide(x, y + 0.1) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: inx = ops.convert_to_tensor(x) iny = ops.convert_to_tensor(y + 0.1) ofunc = inx / iny @@ -156,7 +156,7 @@ class MathBuiltinUnaryTest(test.TestCase): class BroadcastSimpleTest(test.TestCase): def _GetGradientArgs(self, xs, ys): - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: return sess.run(broadcast_gradient_args(xs, ys)) def testBroadcast(self): @@ -210,7 +210,7 @@ class BroadcastSimpleTest(test.TestCase): def _compareGpu(self, x, y, np_func, tf_func): np_ans = np_func(x, y) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(x) iny = ops.convert_to_tensor(y) out = tf_func(inx, iny) diff --git a/tensorflow/python/kernel_tests/batch_gather_op_test.py b/tensorflow/python/kernel_tests/batch_gather_op_test.py index 84e93b8136..547506d844 100644 --- a/tensorflow/python/kernel_tests/batch_gather_op_test.py +++ b/tensorflow/python/kernel_tests/batch_gather_op_test.py @@ -44,7 +44,7 @@ class GatherTest(test.TestCase, parameterized.TestCase): def testSimpleGather(self, indices_dtype): data = np.array([0, 1, 2, 3, 7, 5, 8, 9, 10, 11, 15, 13]) indices = [3, 4] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in _TEST_TYPES: params_np = self._buildParams(data, dtype) params = constant_op.constant(params_np) @@ -60,7 +60,7 @@ class GatherTest(test.TestCase, parameterized.TestCase): def test2DArray(self, indices_dtype): data = np.array([[0, 1, 2, 3, 7, 5], [8, 9, 10, 11, 15, 13]]) indices = [[3], [4]] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in _TEST_TYPES: params_np = self._buildParams(data, dtype) params = constant_op.constant(params_np) @@ -75,7 +75,7 @@ class GatherTest(test.TestCase, parameterized.TestCase): def testHigherRank(self): data = np.array([[[0, 1, 2], [3, 7, 5]], [[8, 9, 10], [11, 15, 13]]]) indices = [[[2, 0], [1, 2]], [[2, 0], [0, 1]]] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in _TEST_TYPES: params_np = self._buildParams(data, dtype) params = constant_op.constant(params_np) @@ -101,13 +101,13 @@ class GatherTest(test.TestCase, parameterized.TestCase): self.assertEqual([1, None], gather_t.get_shape().as_list()) def testBadIndicesCPU(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): params = [[0, 1, 2], [3, 4, 5]] with self.assertRaisesOpError(r"indices\[0\] = 7 is not in \[0, 2\)"): array_ops.batch_gather(params, [7]).eval() def testEmptySlices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in _TEST_TYPES: for itype in np.int32, np.int64: params = np.zeros((7, 0, 0), dtype=dtype.as_numpy_dtype) diff --git a/tensorflow/python/kernel_tests/batch_matmul_op_test.py b/tensorflow/python/kernel_tests/batch_matmul_op_test.py index 34089e8dbe..8f6c089b42 100644 --- a/tensorflow/python/kernel_tests/batch_matmul_op_test.py +++ b/tensorflow/python/kernel_tests/batch_matmul_op_test.py @@ -83,7 +83,7 @@ class BatchMatmulOpTest(test.TestCase): y = y_in if not adjoint_b else y_in.reshape(y_t_shape) is_floating = x.dtype != np.int32 tol = 100 * np.finfo(x.dtype).eps if is_floating else 0 - with self.test_session(use_gpu=is_floating) as sess: + with self.cached_session(use_gpu=is_floating) as sess: if static_shape: z0 = math_ops.matmul(x, y, adjoint_a=adjoint_a, adjoint_b=adjoint_b) z0_val = z0.eval() @@ -154,7 +154,7 @@ class BatchMatmulGradientTest(test.TestCase): y = y_in if not adjoint_b else y_in.reshape(y_t_shape) epsilon = np.finfo(x.dtype).eps delta = epsilon**(1.0 / 3.0) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = constant_op.constant(x) iny = constant_op.constant(y) z = math_ops.matmul(inx, iny, adjoint_a, adjoint_b) diff --git a/tensorflow/python/kernel_tests/batch_scatter_ops_test.py b/tensorflow/python/kernel_tests/batch_scatter_ops_test.py index 498e5f05a3..742a204883 100644 --- a/tensorflow/python/kernel_tests/batch_scatter_ops_test.py +++ b/tensorflow/python/kernel_tests/batch_scatter_ops_test.py @@ -51,7 +51,7 @@ class ScatterTest(test.TestCase): repeat_indices=False, updates_are_scalar=False): np.random.seed(8) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): for indices_shape in (2,), (3, 7), (3, 4, 7): for extra_shape in (), (5,), (5, 9): # Generate random indices with no duplicates for easy numpy comparison @@ -81,7 +81,7 @@ class ScatterTest(test.TestCase): state_ops.batch_scatter_update, vtype, itype) def testBooleanScatterUpdate(self): - with self.test_session(use_gpu=False) as session: + with self.session(use_gpu=False) as session: var = variables.Variable([True, False]) update0 = state_ops.batch_scatter_update(var, [1], [True]) update1 = state_ops.batch_scatter_update( @@ -96,7 +96,7 @@ class ScatterTest(test.TestCase): def testScatterOutOfRange(self): params = np.array([1, 2, 3, 4, 5, 6]).astype(np.float32) updates = np.array([-3, -4, -5]).astype(np.float32) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): ref = variables.Variable(params) ref.initializer.run() diff --git a/tensorflow/python/kernel_tests/bias_op_test.py b/tensorflow/python/kernel_tests/bias_op_test.py index 2427118407..749d6a791e 100644 --- a/tensorflow/python/kernel_tests/bias_op_test.py +++ b/tensorflow/python/kernel_tests/bias_op_test.py @@ -48,7 +48,7 @@ class BiasAddTest(test.TestCase): def _testBias(self, np_inputs, np_bias, use_gpu=False): np_val = self._npBias(np_inputs, np_bias) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_val = nn_ops.bias_add(np_inputs, np_bias).eval() self.assertAllCloseAccordingToType(np_val, tf_val) @@ -76,7 +76,7 @@ class BiasAddTest(test.TestCase): def _testBiasNCHW(self, np_inputs, np_bias, use_gpu): np_val = self._npBias(np_inputs, np_bias) np_inputs = self._NHWCToNCHW(np_inputs) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_val = nn_ops.bias_add(np_inputs, np_bias, data_format="NCHW").eval() tf_val = self._NCHWToNHWC(tf_val) self.assertAllCloseAccordingToType(self._AtLeast3d(np_val), tf_val) @@ -133,7 +133,7 @@ class BiasAddTest(test.TestCase): np.random.rand(4).astype(t)) def _testGradient(self, np_input, bias, dtype, data_format, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): if data_format == "NCHW": np_input = self._NHWCToNCHW(np_input) input_tensor = constant_op.constant( diff --git a/tensorflow/python/kernel_tests/bincount_op_test.py b/tensorflow/python/kernel_tests/bincount_op_test.py index 8177cdd454..49eb835847 100644 --- a/tensorflow/python/kernel_tests/bincount_op_test.py +++ b/tensorflow/python/kernel_tests/bincount_op_test.py @@ -31,7 +31,7 @@ from tensorflow.python.platform import googletest class BincountTest(test_util.TensorFlowTestCase): def test_empty(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual( math_ops.bincount([], minlength=5).eval(), [0, 0, 0, 0, 0]) self.assertAllEqual(math_ops.bincount([], minlength=1).eval(), [0]) @@ -44,7 +44,7 @@ class BincountTest(test_util.TensorFlowTestCase): np.float64) def test_values(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual( math_ops.bincount([1, 1, 1, 2, 2, 3]).eval(), [0, 3, 2, 1]) arr = [1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5] @@ -59,14 +59,14 @@ class BincountTest(test_util.TensorFlowTestCase): math_ops.bincount(np.arange(10000)).eval(), np.ones(10000)) def test_maxlength(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual(math_ops.bincount([5], maxlength=3).eval(), [0, 0, 0]) self.assertAllEqual(math_ops.bincount([1], maxlength=3).eval(), [0, 1]) self.assertAllEqual(math_ops.bincount([], maxlength=3).eval(), []) def test_random_with_weights(self): num_samples = 10000 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): np.random.seed(42) for dtype in [dtypes.int32, dtypes.int64, dtypes.float32, dtypes.float64]: arr = np.random.randint(0, 1000, num_samples) @@ -79,7 +79,7 @@ class BincountTest(test_util.TensorFlowTestCase): def test_random_without_weights(self): num_samples = 10000 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): np.random.seed(42) for dtype in [np.int32, np.float32]: arr = np.random.randint(0, 1000, num_samples) @@ -88,7 +88,7 @@ class BincountTest(test_util.TensorFlowTestCase): math_ops.bincount(arr, None).eval(), np.bincount(arr, weights)) def test_zero_weights(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual( math_ops.bincount(np.arange(1000), np.zeros(1000)).eval(), np.zeros(1000)) diff --git a/tensorflow/python/kernel_tests/bitcast_op_test.py b/tensorflow/python/kernel_tests/bitcast_op_test.py index a2c6b54273..79e0f36d24 100644 --- a/tensorflow/python/kernel_tests/bitcast_op_test.py +++ b/tensorflow/python/kernel_tests/bitcast_op_test.py @@ -28,7 +28,7 @@ from tensorflow.python.platform import test class BitcastTest(test.TestCase): def _testBitcast(self, x, datatype, shape): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tf_ans = array_ops.bitcast(x, datatype) out = tf_ans.eval() buff_after = memoryview(out).tobytes() diff --git a/tensorflow/python/kernel_tests/broadcast_to_ops_test.py b/tensorflow/python/kernel_tests/broadcast_to_ops_test.py index 09c325f2bc..5fe62a70d0 100644 --- a/tensorflow/python/kernel_tests/broadcast_to_ops_test.py +++ b/tensorflow/python/kernel_tests/broadcast_to_ops_test.py @@ -32,21 +32,21 @@ class BroadcastToTest(test_util.TensorFlowTestCase): def testBroadcastToBasic(self): for dtype in [np.uint8, np.uint16, np.int8, np.int16, np.int32, np.int64]: - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = np.array([1, 2, 3], dtype=dtype) v_tf = array_ops.broadcast_to(constant_op.constant(x), [3, 3]) v_np = np.broadcast_to(x, [3, 3]) self.assertAllEqual(v_tf.eval(), v_np) def testBroadcastToString(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = np.array([b"1", b"2", b"3"]) v_tf = array_ops.broadcast_to(constant_op.constant(x), [3, 3]) v_np = np.broadcast_to(x, [3, 3]) self.assertAllEqual(v_tf.eval(), v_np) def testBroadcastToBool(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = np.array([True, False, True], dtype=np.bool) v_tf = array_ops.broadcast_to(constant_op.constant(x), [3, 3]) v_np = np.broadcast_to(x, [3, 3]) @@ -55,7 +55,7 @@ class BroadcastToTest(test_util.TensorFlowTestCase): def testBroadcastToShape(self): for input_dim in range(1, 6): for output_dim in range(input_dim, 6): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): input_shape = [2] * input_dim output_shape = [2] * output_dim x = np.array(np.random.randint(5, size=input_shape), dtype=np.int32) @@ -64,7 +64,7 @@ class BroadcastToTest(test_util.TensorFlowTestCase): self.assertAllEqual(v_tf.eval(), v_np) def testBroadcastToScalar(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = np.array(1, dtype=np.int32) v_tf = array_ops.broadcast_to(constant_op.constant(x), [3, 3]) v_np = np.broadcast_to(x, [3, 3]) @@ -72,7 +72,7 @@ class BroadcastToTest(test_util.TensorFlowTestCase): def testBroadcastToShapeTypeAndInference(self): for dtype in [dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = np.array([1, 2, 3]) v_tf = array_ops.broadcast_to( constant_op.constant(x), diff --git a/tensorflow/python/kernel_tests/bucketize_op_test.py b/tensorflow/python/kernel_tests/bucketize_op_test.py index e612b1c134..57413e6af5 100644 --- a/tensorflow/python/kernel_tests/bucketize_op_test.py +++ b/tensorflow/python/kernel_tests/bucketize_op_test.py @@ -31,7 +31,7 @@ class BucketizationOpTest(test.TestCase): constant_op.constant([-5, 0, 2, 3, 5, 8, 10, 11, 12]), boundaries=[0, 3, 8, 11]) expected_out = [0, 1, 1, 2, 2, 3, 3, 4, 4] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: self.assertAllEqual(expected_out, sess.run(op)) def testFloat(self): @@ -39,7 +39,7 @@ class BucketizationOpTest(test.TestCase): constant_op.constant([-5., 0., 2., 3., 5., 8., 10., 11., 12.]), boundaries=[0., 3., 8., 11.]) expected_out = [0, 1, 1, 2, 2, 3, 3, 4, 4] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: self.assertAllEqual(expected_out, sess.run(op)) def test2DInput(self): @@ -47,13 +47,13 @@ class BucketizationOpTest(test.TestCase): constant_op.constant([[-5, 0, 2, 3, 5], [8, 10, 11, 12, 0]]), boundaries=[0, 3, 8, 11]) expected_out = [[0, 1, 1, 2, 2], [3, 3, 4, 4, 1]] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: self.assertAllEqual(expected_out, sess.run(op)) def testInvalidBoundariesOrder(self): op = math_ops._bucketize( constant_op.constant([-5, 0]), boundaries=[0, 8, 3, 11]) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: with self.assertRaisesRegexp( errors_impl.InvalidArgumentError, "Expected sorted boundaries"): sess.run(op) diff --git a/tensorflow/python/kernel_tests/cast_op_test.py b/tensorflow/python/kernel_tests/cast_op_test.py index c90520e46d..a5dff5df62 100644 --- a/tensorflow/python/kernel_tests/cast_op_test.py +++ b/tensorflow/python/kernel_tests/cast_op_test.py @@ -54,7 +54,7 @@ class CastOpTest(test.TestCase): return None def _cast(self, x, dtype, use_gpu=False): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): val = constant_op.constant(x, self._toDataType(np.array([x]).dtype)) return math_ops.cast(val, self._toDataType(dtype), name="cast").eval() @@ -105,10 +105,10 @@ class CastOpTest(test.TestCase): def testBfloat16(self): a = np.random.uniform(-100, 100, 100).astype(np.float32) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): b = math_ops.cast(math_ops.cast(a, dtypes.bfloat16), dtypes.float32) self.assertAllClose(a, b.eval(), rtol=1 / 128.) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): b = math_ops.cast(math_ops.cast(a, dtypes.bfloat16), dtypes.float32) self.assertAllClose(a, b.eval(), rtol=1 / 128.) diff --git a/tensorflow/python/kernel_tests/cholesky_op_test.py b/tensorflow/python/kernel_tests/cholesky_op_test.py index 2ebf74a4d7..e96b277266 100644 --- a/tensorflow/python/kernel_tests/cholesky_op_test.py +++ b/tensorflow/python/kernel_tests/cholesky_op_test.py @@ -111,7 +111,7 @@ class CholeskyOpTest(test.TestCase): def _verifyCholesky(self, x): # Verify that LL^T == x. - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: chol = linalg_ops.cholesky(x) verification = math_ops.matmul(chol, chol, adjoint_b=True) self._verifyCholeskyBase(sess, x, chol, verification) @@ -162,7 +162,7 @@ class CholeskyOpTest(test.TestCase): def testNotInvertibleCPU(self): # The input should be invertible. - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesRegexp( errors_impl.InvalidArgumentError, "Cholesky decomposition was not successful. The" @@ -176,7 +176,7 @@ class CholeskyOpTest(test.TestCase): self._verifyCholesky(np.empty([2, 0, 0])) def testConcurrentExecutesWithoutError(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: matrix1 = random_ops.random_normal([5, 5], seed=42) matrix2 = random_ops.random_normal([5, 5], seed=42) matrix1 = math_ops.matmul(matrix1, matrix1, adjoint_a=True) @@ -243,7 +243,7 @@ class CholeskyGradTest(test.TestCase): data = np.matmul(data, data.T) grad_data = np.random.randn(*data.shape).astype(np.float32) - with ops.Graph().as_default(), self.test_session(use_gpu=False) as s: + with ops.Graph().as_default(), self.session(use_gpu=False) as s: x = constant_op.constant(data, dtypes_lib.float32) chol = linalg_ops.cholesky(x) composite_grad = gradients_impl.gradients(chol, x, grad_data)[0] @@ -256,7 +256,7 @@ class CholeskyGradTest(test.TestCase): dtypes=(dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.complex64, dtypes_lib.complex128), scalarTest=False): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in shapes: for batch in False, True: for dtype in dtypes: diff --git a/tensorflow/python/kernel_tests/clip_ops_test.py b/tensorflow/python/kernel_tests/clip_ops_test.py index bb7b645da2..efd7eee847 100644 --- a/tensorflow/python/kernel_tests/clip_ops_test.py +++ b/tensorflow/python/kernel_tests/clip_ops_test.py @@ -50,7 +50,7 @@ class ClipTest(test.TestCase): # ClipByValue test def testClipByValue(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-5.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3]) np_ans = [[-4.4, 2.0, 3.0], [4.0, 4.4, 4.4]] clip_value = 4.4 @@ -65,7 +65,7 @@ class ClipTest(test.TestCase): dtypes.float16, dtypes.float32, dtypes.float64, dtypes.int8, dtypes.int16, dtypes.int32, dtypes.int64, dtypes.uint8, dtypes.uint16 ]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = constant_op.constant([1, 2, 3, 4, 5, 6], shape=[2, 3], dtype=dtype) np_ans = [[2, 2, 3], [4, 4, 4]] clip_value_min = 2 @@ -81,7 +81,7 @@ class ClipTest(test.TestCase): dtypes.float16, dtypes.float32, dtypes.float64, dtypes.int8, dtypes.int16, dtypes.int32, dtypes.int64, dtypes.uint8, dtypes.uint16 ]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = constant_op.constant([1, 2, 3, 4, 5, 6], shape=[2, 3], dtype=dtype) np_ans = [[2, 2, 3], [4, 4, 4]] clip_value_min = constant_op.constant( @@ -98,7 +98,7 @@ class ClipTest(test.TestCase): dtypes.float16, dtypes.float32, dtypes.float64, dtypes.int8, dtypes.int16, dtypes.int32, dtypes.int64, dtypes.uint8, dtypes.uint16 ]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = constant_op.constant([1, 2, 3, 4, 5, 6], shape=[2, 3], dtype=dtype) np_ans = [[4, 4, 4], [4, 5, 6]] clip_value_min = 4 @@ -115,7 +115,7 @@ class ClipTest(test.TestCase): dtypes.float16, dtypes.float32, dtypes.float64, dtypes.int8, dtypes.int16, dtypes.int32, dtypes.int64, dtypes.uint8, dtypes.uint16 ]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = constant_op.constant([1, 2, 3, 4, 5, 6], shape=[2, 3], dtype=dtype) np_ans = [[2, 2, 3], [5, 5, 6]] clip_value_min = constant_op.constant( @@ -128,7 +128,7 @@ class ClipTest(test.TestCase): self.assertAllClose(np_ans, tf_ans) def testClipByValueBadShape(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-5.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3, 1]) # Use a nonsensical shape. clip = constant_op.constant([1.0, 2.0]) @@ -151,7 +151,7 @@ class ClipTest(test.TestCase): # ClipByNorm tests def testClipByNormClipped(self): # Norm clipping when clip_norm < 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Norm of x = sqrt(3^2 + 4^2) = 5 np_ans = [[-2.4, 0.0, 0.0], [3.2, 0.0, 0.0]] @@ -166,14 +166,14 @@ class ClipTest(test.TestCase): self.assertAllClose(np_ans, tf_ans_tensor) def testClipByNormGradientZeros(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = array_ops.zeros([3]) b = clip_ops.clip_by_norm(x, 1.) grad, = gradients_impl.gradients(b, x) self.assertAllEqual(grad.eval(), [1., 1., 1.]) def testClipByNormBadShape(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3, 1]) # Use a nonsensical shape. clip = constant_op.constant([1.0, 2.0]) @@ -182,7 +182,7 @@ class ClipTest(test.TestCase): def testClipByNormNotClipped(self): # No norm clipping when clip_norm >= 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Norm of x = sqrt(3^2 + 4^2) = 5 np_ans = [[-3.0, 0.0, 0.0], [4.0, 0.0, 0.0]] @@ -194,7 +194,7 @@ class ClipTest(test.TestCase): def testClipByNormZero(self): # No norm clipping when norm = 0 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([0.0, 0.0, 0.0, 0.0, 0.0, 0.0], shape=[2, 3]) # Norm = 0, no changes np_ans = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] @@ -206,7 +206,7 @@ class ClipTest(test.TestCase): def testClipByNormClippedWithDim0(self): # Norm clipping when clip_norm < 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 3.0], shape=[2, 3]) # Norm of x[:, 0] = sqrt(3^2 + 4^2) = 5, x[:, 2] = 3 np_ans = [[-2.4, 0.0, 0.0], [3.2, 0.0, 3.0]] @@ -218,7 +218,7 @@ class ClipTest(test.TestCase): def testClipByNormClippedWithDim1(self): # Norm clipping when clip_norm < 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 3.0], shape=[2, 3]) # Norm of x[0, :] = 3, x[1, :] = sqrt(3^2 + 4^2) = 5 np_ans = [[-3.0, 0.0, 0.0], [3.2, 0.0, 2.4]] @@ -230,7 +230,7 @@ class ClipTest(test.TestCase): def testClipByNormNotClippedWithAxes(self): # No norm clipping when clip_norm >= 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 3.0], shape=[2, 3]) # Norm of x[0, :] = 3, x[1, :] = sqrt(3^2 + 4^2) = 5 np_ans = [[-3.0, 0.0, 0.0], [4.0, 0.0, 3.0]] @@ -243,7 +243,7 @@ class ClipTest(test.TestCase): # ClipByGlobalNorm tests def testClipByGlobalNormClipped(self): # Norm clipping when clip_norm < 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) # Global norm of x0 and x1 = sqrt(1 + 4^2 + 2^2 + 2^2) = 5 @@ -264,7 +264,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormClippedTensor(self): # Norm clipping when clip_norm < 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) # Global norm of x0 and x1 = sqrt(1 + 4^2 + 2^2 + 2^2) = 5 @@ -285,7 +285,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormSupportsNone(self): # Norm clipping when clip_norm < 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) # Global norm of x0 and x1 = sqrt(1 + 4^2 + 2^2 + 2^2) = 5 @@ -308,7 +308,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormWithIndexedSlicesClipped(self): # Norm clipping when clip_norm < 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = ops.IndexedSlices( constant_op.constant([1.0, -2.0]), constant_op.constant([3, 4])) @@ -341,7 +341,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormNotClipped(self): # No norm clipping when clip_norm >= 5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) # Global norm of x0 and x1 = sqrt(1 + 4^2 + 2^2 + 2^2) = 5 @@ -360,7 +360,7 @@ class ClipTest(test.TestCase): def testClipByGlobalNormZero(self): # No norm clipping when norm = 0 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x0 = constant_op.constant([0.0, 0.0, 0.0, 0.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([0.0, 0.0]) # Norm = 0, no changes @@ -378,7 +378,7 @@ class ClipTest(test.TestCase): self.assertAllClose(np_ans_1, tf_ans_2) def testClipByGlobalNormInf(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x0 = constant_op.constant([-2.0, 0.0, np.inf, 4.0, 0.0, 0.0], shape=[2, 3]) x1 = constant_op.constant([1.0, -2.0]) @@ -394,7 +394,7 @@ class ClipTest(test.TestCase): def testClipByAverageNormClipped(self): # Norm clipping when average clip_norm < 0.83333333 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Average norm of x = sqrt(3^2 + 4^2) / 6 = 0.83333333 np_ans = [[-2.88, 0.0, 0.0], [3.84, 0.0, 0.0]] @@ -406,7 +406,7 @@ class ClipTest(test.TestCase): def testClipByAverageNormClippedTensor(self): # Norm clipping when average clip_norm < 0.83333333 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Average norm of x = sqrt(3^2 + 4^2) / 6 = 0.83333333 np_ans = [[-2.88, 0.0, 0.0], [3.84, 0.0, 0.0]] @@ -418,7 +418,7 @@ class ClipTest(test.TestCase): def testClipByAverageNormNotClipped(self): # No norm clipping when average clip_norm >= 0.83333333 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([-3.0, 0.0, 0.0, 4.0, 0.0, 0.0], shape=[2, 3]) # Average norm of x = sqrt(3^2 + 4^2) / 6 = 0.83333333 np_ans = [[-3.0, 0.0, 0.0], [4.0, 0.0, 0.0]] @@ -430,7 +430,7 @@ class ClipTest(test.TestCase): def testClipByAverageNormZero(self): # No norm clipping when average clip_norm = 0 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = constant_op.constant([0.0, 0.0, 0.0, 0.0, 0.0, 0.0], shape=[2, 3]) # Average norm = 0, no changes np_ans = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] @@ -447,7 +447,7 @@ class ClipTest(test.TestCase): y = clip_ops.clip_by_value(zero, 1.0, 1.0) z = clip_ops.clip_by_value(zero, zero, 1.0) w = clip_ops.clip_by_value(zero, 1.0, zero) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run([x, y, z, w], feed_dict={zero: np.zeros((7, 0))}) diff --git a/tensorflow/python/kernel_tests/compare_and_bitpack_op_test.py b/tensorflow/python/kernel_tests/compare_and_bitpack_op_test.py index 56ddd6e428..f27a0fc472 100644 --- a/tensorflow/python/kernel_tests/compare_and_bitpack_op_test.py +++ b/tensorflow/python/kernel_tests/compare_and_bitpack_op_test.py @@ -30,7 +30,7 @@ class CompareAndBitpackTest(test.TestCase): x, threshold, truth, expected_err_re=None): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ans = math_ops.compare_and_bitpack(x, threshold) if expected_err_re is None: tf_ans = ans.eval() diff --git a/tensorflow/python/kernel_tests/concat_op_test.py b/tensorflow/python/kernel_tests/concat_op_test.py index 0e59ce6972..92d09986e6 100644 --- a/tensorflow/python/kernel_tests/concat_op_test.py +++ b/tensorflow/python/kernel_tests/concat_op_test.py @@ -35,7 +35,7 @@ from tensorflow.python.platform import test class ConcatOpTest(test.TestCase): def testHStack(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): p1 = array_ops.placeholder(dtypes.float32, shape=[4, 4]) p2 = array_ops.placeholder(dtypes.float32, shape=[4, 4]) c = array_ops.concat([p1, p2], 0) @@ -50,7 +50,7 @@ class ConcatOpTest(test.TestCase): self.assertAllEqual(result[4:, :], params[p2]) def testVStack(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): p1 = array_ops.placeholder(dtypes.float32, shape=[4, 4]) p2 = array_ops.placeholder(dtypes.float32, shape=[4, 4]) c = array_ops.concat([p1, p2], 1) @@ -65,7 +65,7 @@ class ConcatOpTest(test.TestCase): self.assertAllEqual(result[:, 4:], params[p2]) def testInt32GPU(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): p1 = np.random.rand(2, 3).astype("i") p2 = np.random.rand(2, 3).astype("i") x1 = constant_op.constant(p1) @@ -76,7 +76,7 @@ class ConcatOpTest(test.TestCase): self.assertAllEqual(result[2:, :], p2) def testRefType(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): p1 = np.random.rand(4, 4).astype("f") p2 = np.random.rand(4, 4).astype("f") v1 = variables.Variable(p1) @@ -101,7 +101,7 @@ class ConcatOpTest(test.TestCase): dtype_feed = dtypes.float32 else: dtype_feed = dtype - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): p = [] for i in np.arange(num_tensors): input_shape = shape @@ -172,7 +172,7 @@ class ConcatOpTest(test.TestCase): # Test both positive and negative concat axis. # -2 and 1 correspond to the same axis for 3-dimensional tensors. for axis in [-2, 1]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = [] inp_tensors = [] for x in [1, 2, 6]: @@ -203,7 +203,7 @@ class ConcatOpTest(test.TestCase): self._testGradientsSimple(dtypes.complex64) def testGradientsFirstDim(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): inp = [] inp_tensors = [] for x in [1, 2, 6]: @@ -230,7 +230,7 @@ class ConcatOpTest(test.TestCase): # Test both positive and negative concat axis. # -1 and 2 correspond to the same axis for 3-dimensional tensors. for axis in [-1, 2]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = [] inp_tensors = [] for x in [1, 2, 6]: @@ -261,7 +261,7 @@ class ConcatOpTest(test.TestCase): # Random dim to concat on concat_dim = np.random.randint(5) concat_dim_sizes = np.random.randint(1, 5, size=num_tensors) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = [] inp_tensors = [] for x in concat_dim_sizes: @@ -288,7 +288,7 @@ class ConcatOpTest(test.TestCase): self._RunAndVerifyGradientsRandom() def testGradientWithUnknownInputDim(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = array_ops.placeholder(dtypes.float32) y = array_ops.placeholder(dtypes.float32) c = array_ops.concat([x, y], 2) @@ -358,7 +358,7 @@ class ConcatOpTest(test.TestCase): def testZeroSize(self): # Verify that concat doesn't crash and burn for zero size inputs np.random.seed(7) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: for shape0 in (), (2,): axis = len(shape0) for shape1 in (), (3,): @@ -489,7 +489,7 @@ class ConcatOpTest(test.TestCase): # important as gpu implementation could fail if # shared memory is not large for all the inputs def testConcatLargeNumberOfTensors(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for concat_dim in range(2): params = {} p = [] @@ -523,7 +523,7 @@ class ConcatOpTest(test.TestCase): self.assertAllEqual(result[index], params[p[i]]) def testConcatEmpty(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): t1 = [] t2 = [] output = gen_array_ops.concat_v2([t1, t2], 0).eval() @@ -531,13 +531,13 @@ class ConcatOpTest(test.TestCase): def testConcatInvalidAxis(self): with self.assertRaises(ValueError): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): t1 = [1] t2 = [2] gen_array_ops.concat_v2([t1, t2], 1).eval() def testConcatNegativeAxis(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): t1 = [[1, 2, 3], [4, 5, 6]] t2 = [[7, 8, 9], [10, 11, 12]] @@ -608,7 +608,7 @@ class ConcatOpTest(test.TestCase): def testConcatAxisType(self): for dtype in [dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): t1 = [[1, 2, 3], [4, 5, 6]] t2 = [[7, 8, 9], [10, 11, 12]] @@ -621,7 +621,7 @@ class ConcatOpTest(test.TestCase): class ConcatOffsetTest(test.TestCase): def testBasic(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: cdim = constant_op.constant(1, dtypes.int32) s0 = constant_op.constant([2, 3, 5], dtypes.int32) s1 = constant_op.constant([2, 7, 5], dtypes.int32) @@ -673,7 +673,7 @@ class ConcatOffsetTest(test.TestCase): sess.run(off) def testNegativeDim(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: cdim = constant_op.constant(-2, dtypes.int32) s0 = constant_op.constant([2, 3, 5], dtypes.int32) s1 = constant_op.constant([2, 7, 5], dtypes.int32) diff --git a/tensorflow/python/kernel_tests/cond_v2_test.py b/tensorflow/python/kernel_tests/cond_v2_test.py index 85a5986041..df50fce2ef 100644 --- a/tensorflow/python/kernel_tests/cond_v2_test.py +++ b/tensorflow/python/kernel_tests/cond_v2_test.py @@ -849,7 +849,7 @@ class CondV2ColocationGroupAndDeviceTest(test.TestCase): def testColocateWithInCondGraphPartitioning(self): with ops.Graph().as_default() as g: - with self.test_session( + with self.session( graph=g, config=config_pb2.ConfigProto(device_count={"CPU": 2}) ) as sess: @@ -904,7 +904,7 @@ class CondV2ColocationGroupAndDeviceTest(test.TestCase): def testDeviceInAndOutOfCond(self): with ops.Graph().as_default() as g: - with self.test_session( + with self.session( graph=g, config=config_pb2.ConfigProto(device_count={"CPU": 2})): def fn2(): @@ -922,7 +922,7 @@ class CondV2ColocationGroupAndDeviceTest(test.TestCase): def testDeviceInCondGraphPartitioning(self): with ops.Graph().as_default() as g: - with self.test_session( + with self.session( graph=g, config=config_pb2.ConfigProto(device_count={"CPU": 2}) ) as sess: diff --git a/tensorflow/python/kernel_tests/constant_op_test.py b/tensorflow/python/kernel_tests/constant_op_test.py index d1e4e5477f..403d5eaf9a 100644 --- a/tensorflow/python/kernel_tests/constant_op_test.py +++ b/tensorflow/python/kernel_tests/constant_op_test.py @@ -43,7 +43,7 @@ class ConstantTest(test.TestCase): def _testCpu(self, x): np_ans = np.array(x) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): tf_ans = ops.convert_to_tensor(x).eval() dtype = dtypes_lib.as_dtype(np_ans.dtype) if dtype.is_floating or dtype.is_complex: @@ -53,7 +53,7 @@ class ConstantTest(test.TestCase): def _testGpu(self, x): np_ans = np.array(x) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_ans = ops.convert_to_tensor(x).eval() dtype = dtypes_lib.as_dtype(np_ans.dtype) if dtype.is_floating or dtype.is_complex: @@ -134,7 +134,7 @@ class ConstantTest(test.TestCase): def testVariant(self): # TODO(ebrevdo): Re-enable use_gpu=True once non-DMA Variant # copying between CPU and GPU is supported. - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): variant_tensor = tensor_pb2.TensorProto( dtype=dtypes_lib.variant.as_datatype_enum, tensor_shape=tensor_shape.TensorShape([]).as_proto(), @@ -432,7 +432,7 @@ class ZerosTest(test.TestCase): class ZerosLikeTest(test.TestCase): def _compareZeros(self, dtype, fully_defined_shape, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): # Creates a tensor of non-zero values with shape 2 x 3. # NOTE(kearnes): The default numpy dtype associated with tf.string is # np.object (and can't be changed without breaking a lot things), which @@ -505,7 +505,7 @@ class ZerosLikeTest(test.TestCase): # copying between CPU and GPU is supported AND we register a # ZerosLike callback for GPU for Variant storing primitive types # in variant_op_registry.cc. - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): variant_tensor = tensor_pb2.TensorProto( dtype=dtypes_lib.variant.as_datatype_enum, tensor_shape=tensor_shape.TensorShape([]).as_proto(), @@ -630,7 +630,7 @@ class OnesLikeTest(test.TestCase): class FillTest(test.TestCase): def _compare(self, dims, val, np_ans, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_ans = array_ops.fill(dims, val, name="fill") out = tf_ans.eval() self.assertAllClose(np_ans, out) @@ -667,7 +667,7 @@ class FillTest(test.TestCase): def testFillString(self): np_ans = np.array([[b"yolo"] * 3] * 2) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): tf_ans = array_ops.fill([2, 3], np_ans[0][0], name="fill").eval() self.assertAllEqual(np_ans, tf_ans) @@ -886,7 +886,7 @@ versions { class PlaceholderWithDefaultTest(test.TestCase): def testFullShape(self): - with self.test_session(force_gpu=test_util.is_gpu_available()): + with self.session(force_gpu=test_util.is_gpu_available()): p = array_ops.placeholder_with_default([[2, 2], [2, 2]], shape=[2, 2]) a = array_ops.identity(p) self.assertAllEqual([[2, 2], [2, 2]], a.eval()) @@ -897,7 +897,7 @@ class PlaceholderWithDefaultTest(test.TestCase): a.eval(feed_dict={p: [[6, 6, 6], [6, 6, 6]]}) def testPartialShape(self): - with self.test_session(force_gpu=test_util.is_gpu_available()): + with self.session(force_gpu=test_util.is_gpu_available()): p = array_ops.placeholder_with_default([1, 2, 3], shape=[None]) a = array_ops.identity(p) self.assertAllEqual([1, 2, 3], a.eval()) @@ -907,7 +907,7 @@ class PlaceholderWithDefaultTest(test.TestCase): a.eval(feed_dict={p: [[2, 2], [2, 2]]}) def testNoShape(self): - with self.test_session(force_gpu=test_util.is_gpu_available()): + with self.session(force_gpu=test_util.is_gpu_available()): p = array_ops.placeholder_with_default([17], shape=None) a = array_ops.identity(p) self.assertAllEqual([17], a.eval()) @@ -916,7 +916,7 @@ class PlaceholderWithDefaultTest(test.TestCase): [[3, 3], [3, 3]], a.eval(feed_dict={p: [[3, 3], [3, 3]]})) def testGradient(self): - with self.test_session(force_gpu=test_util.is_gpu_available()): + with self.session(force_gpu=test_util.is_gpu_available()): x = array_ops.placeholder(dtypes_lib.float32, [5, 7]) y = array_ops.placeholder_with_default(x, None) err = gradient_checker.compute_gradient_error(x, [5, 7], y, [5, 7]) diff --git a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py index 3c7e6e6dce..1e9f29028b 100644 --- a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py +++ b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py @@ -456,7 +456,7 @@ class ControlFlowTest(test.TestCase): self.assertTrue(ind.dtype == np.int64) def testCondColocation(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with ops.device("/cpu:0"): v = variables.Variable(7.0) @@ -471,7 +471,7 @@ class ControlFlowTest(test.TestCase): self.assertDeviceEqual(op.device, "/cpu:0") def _testCond_1(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): x = constant_op.constant(10) pred = math_ops.less(1, 2) fn1 = lambda: math_ops.add(x, 1) @@ -1023,7 +1023,7 @@ class ControlFlowTest(test.TestCase): final_without_xla_context = create_while_loop() - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: opts = config_pb2.RunOptions(trace_level=config_pb2.RunOptions.FULL_TRACE) run_metadata = config_pb2.RunMetadata() @@ -1139,7 +1139,7 @@ class ControlFlowTest(test.TestCase): self.assertLess(len(unique_allocs), 756) def _testWhile_Gpu_1(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): n = constant_op.constant(1.0) c = lambda x: math_ops.less(x, 10.0) b = lambda x: math_ops.add(x, 1.0) @@ -1151,7 +1151,7 @@ class ControlFlowTest(test.TestCase): self._testWhile_Gpu_1(use_gpu=True) def _testWhile_Gpu_2(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): n = constant_op.constant(1.0) c = lambda x: math_ops.less(x, 10.0) @@ -1293,7 +1293,7 @@ class ControlFlowTest(test.TestCase): [i.get_shape(), tensor_shape.TensorShape([None, 5])]) def _testNestedWhile_1(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): n = constant_op.constant(0) def cpu_sum(s): @@ -1320,7 +1320,7 @@ class ControlFlowTest(test.TestCase): def _testNestedWhile_2(self, use_gpu): # Test the cases that A -> Enter and Exit -> A are partitioned. - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): s0 = constant_op.constant(2.0) def inner_loop(s): @@ -1499,7 +1499,7 @@ class ControlFlowTest(test.TestCase): self.assertAllEqual(10, r.eval()) def _testCondWhile_3(self, use_gpu): - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: p = array_ops.placeholder(dtypes.bool) n = constant_op.constant(0.0) @@ -1881,7 +1881,7 @@ class ControlFlowTest(test.TestCase): self.assertAllClose(2048.0, r.eval()) def _testWhileGrad_Mul(self, use_gpu, p_iters): - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: a = constant_op.constant(3.0, name="a") v = constant_op.constant(2.0, name="v") c = lambda v: math_ops.less(v, 100.0) @@ -1901,7 +1901,7 @@ class ControlFlowTest(test.TestCase): def _testNestedWhileCondWhileGrad(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): v = constant_op.constant(1.0) def inner_loop(s): @@ -2207,7 +2207,7 @@ class ControlFlowTest(test.TestCase): self.assertAllClose(1.0, g.eval()) # y_f_d = x + 1.0, dy_f_d/dx = 1.0 def _testNestedWhileGrad_Simple(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): v = constant_op.constant(1.0) def inner_loop(s): @@ -2300,7 +2300,7 @@ class ControlFlowTest(test.TestCase): self.assertAllClose(2.999, var.eval()) def _testWhileCondGrad_Simple(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): v = ops.convert_to_tensor(2.0, name="v") n = ops.convert_to_tensor(100.0, name="n") one = ops.convert_to_tensor(1.0, name="one") @@ -3299,7 +3299,7 @@ class TupleTest(test.TestCase): class AssertTest(test.TestCase): def testGuardedAssertDoesNotCopyWhenTrue(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: with ops.device(test.gpu_device_name()): value = constant_op.constant(1.0) with ops.device("/cpu:0"): diff --git a/tensorflow/python/kernel_tests/conv1d_test.py b/tensorflow/python/kernel_tests/conv1d_test.py index 2d6d8a8051..8540875d75 100644 --- a/tensorflow/python/kernel_tests/conv1d_test.py +++ b/tensorflow/python/kernel_tests/conv1d_test.py @@ -40,7 +40,7 @@ class Conv1DTest(test.TestCase): filters = array_ops.expand_dims(filters, 2) # out_channels # Filters is 2x1x1 for stride in [1, 2]: - with self.test_session(use_gpu=test.is_gpu_available()): + with self.cached_session(use_gpu=test.is_gpu_available()): c = nn_ops.conv1d(x, filters, stride, padding="VALID") reduced = array_ops.squeeze(c) output = reduced.eval() diff --git a/tensorflow/python/kernel_tests/conv2d_backprop_filter_grad_test.py b/tensorflow/python/kernel_tests/conv2d_backprop_filter_grad_test.py index 644a151710..af6ffc1d19 100644 --- a/tensorflow/python/kernel_tests/conv2d_backprop_filter_grad_test.py +++ b/tensorflow/python/kernel_tests/conv2d_backprop_filter_grad_test.py @@ -66,7 +66,7 @@ class Conv2DBackpropFilterGradTest(test.TestCase): def testGradientDilatedConv(self): if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for padding in ["SAME", "VALID"]: for stride in [1, 2]: np.random.seed(1) diff --git a/tensorflow/python/kernel_tests/conv2d_transpose_test.py b/tensorflow/python/kernel_tests/conv2d_transpose_test.py index cbdd2c5991..6f9992a317 100644 --- a/tensorflow/python/kernel_tests/conv2d_transpose_test.py +++ b/tensorflow/python/kernel_tests/conv2d_transpose_test.py @@ -177,7 +177,7 @@ class Conv2DTransposeTest(test.TestCase): def testConv2DTransposeSingleStrideNCHW(self): # `NCHW` data format is only supported for CUDA device. if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): strides = [1, 1, 1, 1] # Input, output: [batch, depth, height, width, depth] @@ -212,7 +212,7 @@ class Conv2DTransposeTest(test.TestCase): def testConv2DTransposeSameNCHW(self): # `NCHW` data format is only supported for CUDA device. if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): strides = [1, 1, 2, 2] # Input, output: [batch, depth, height, width] @@ -248,7 +248,7 @@ class Conv2DTransposeTest(test.TestCase): def testConv2DTransposeValidNCHW(self): # `NCHW` data format is only supported for CUDA device. if test.is_gpu_available(cuda_only=True): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): strides = [1, 1, 2, 2] # Input, output: [batch, depth, height, width] diff --git a/tensorflow/python/kernel_tests/conv_ops_3d_test.py b/tensorflow/python/kernel_tests/conv_ops_3d_test.py index 6794464e3a..57b09dc167 100644 --- a/tensorflow/python/kernel_tests/conv_ops_3d_test.py +++ b/tensorflow/python/kernel_tests/conv_ops_3d_test.py @@ -74,7 +74,7 @@ class Conv3DTest(test.TestCase): # during the conv3d. x1 = [f * 1.0 / total_size_tensor for f in range(1, total_size_tensor + 1)] x2 = [f * 1.0 / total_size_filter for f in range(1, total_size_filter + 1)] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): t1 = constant_op.constant(x1, shape=tensor_in_sizes, dtype=dtype) t2 = constant_op.constant(x2, shape=filter_in_sizes, dtype=dtype) @@ -133,7 +133,7 @@ class Conv3DTest(test.TestCase): # numbers from 1. x1 = [f * 1.0 for f in range(1, total_size_tensor + 1)] x2 = [f * 1.0 for f in range(1, total_size_filter + 1)] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): t1 = constant_op.constant(x1, shape=tensor_in_sizes) t2 = constant_op.constant(x2, shape=filter_in_sizes) if isinstance(stride, collections.Iterable): @@ -413,7 +413,7 @@ class Conv3DTest(test.TestCase): elif data_type == dtypes.float16: tolerance = 1e-3 - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): orig_input_tensor = constant_op.constant( input_data, shape=input_shape, dtype=data_type, name="input") filter_tensor = constant_op.constant( @@ -659,7 +659,7 @@ class Conv3DTest(test.TestCase): # because we currently do not have a CPU implementation for arbitrary # dilation rates. if default_dilations or use_gpu: - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: if data_format == "NCDHW": input_sizes = test_util.NHWCToNCHW(input_sizes) t1 = constant_op.constant(x1, shape=input_sizes) diff --git a/tensorflow/python/kernel_tests/conv_ops_test.py b/tensorflow/python/kernel_tests/conv_ops_test.py index ea611497d9..0ccbbf155c 100644 --- a/tensorflow/python/kernel_tests/conv_ops_test.py +++ b/tensorflow/python/kernel_tests/conv_ops_test.py @@ -878,7 +878,7 @@ class Conv2DTest(test.TestCase): x2 = [f * 1.0 for f in range(1, total_filter_size + 1)] default_dilations = (dilations[0] == 1 and dilations[1] == 1) if default_dilations or use_gpu: - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: if data_format == "NCHW": input_sizes = test_util.NHWCToNCHW(input_sizes) t1 = constant_op.constant(x1, shape=input_sizes) @@ -932,7 +932,7 @@ class Conv2DTest(test.TestCase): x2 = [f * 1.0 for f in range(1, total_filter_size + 1)] default_dilations = (dilations[0] == 1 and dilations[1] == 1) if default_dilations or use_gpu: - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: if data_format == "NCHW": input_sizes = test_util.NHWCToNCHW(input_sizes) t1 = constant_op.constant(x1, shape=input_sizes) @@ -1139,7 +1139,7 @@ class Conv2DTest(test.TestCase): # So we disable the DOUBLE path. We should re-enable this # when double support returns for CPU and/or GPU. for dtype in self._DtypesToTest(use_gpu=use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): input_tensor = constant_op.constant( input_data, shape=input_shape, dtype=dtype, name="input") filter_tensor = constant_op.constant( @@ -1644,7 +1644,7 @@ class SeparableConv2DTest(test.TestCase): expected: An array containing the expected operation outputs. data_format: string data format for input tensor. """ - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: t1 = self._InitValues(tensor_in_sizes) f1 = self._InitValues(depthwise_filter_in_sizes) f1.set_shape(depthwise_filter_in_sizes) @@ -1766,7 +1766,7 @@ class DeepConv2DTest(test.TestCase): x1 = np.random.rand(*tensor_in_sizes).astype(np.float32) x2 = np.random.rand(*filter_in_sizes).astype(np.float32) - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: t1 = constant_op.constant(x1, shape=tensor_in_sizes) t2 = constant_op.constant(x2, shape=filter_in_sizes) strides = [1] + conv_strides + [1] diff --git a/tensorflow/python/kernel_tests/ctc_decoder_ops_test.py b/tensorflow/python/kernel_tests/ctc_decoder_ops_test.py index 41ae0b456f..d818fbd75c 100644 --- a/tensorflow/python/kernel_tests/ctc_decoder_ops_test.py +++ b/tensorflow/python/kernel_tests/ctc_decoder_ops_test.py @@ -57,7 +57,7 @@ class CTCGreedyDecoderTest(test.TestCase): # from a len time python list of [batch_size x depth] tensors inputs_t = array_ops.stack(inputs_t) - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: decoded_list, log_probability = decoder( inputs_t, sequence_length=seq_lens, **decoder_args) decoded_unwrapped = list( diff --git a/tensorflow/python/kernel_tests/ctc_loss_op_test.py b/tensorflow/python/kernel_tests/ctc_loss_op_test.py index 18e92162b9..cfc7cb98aa 100644 --- a/tensorflow/python/kernel_tests/ctc_loss_op_test.py +++ b/tensorflow/python/kernel_tests/ctc_loss_op_test.py @@ -65,7 +65,7 @@ class CTCLossTest(test.TestCase): inputs_t = constant_op.constant(inputs) - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: loss = ctc_ops.ctc_loss( inputs=inputs_t, labels=labels, sequence_length=seq_lens) grad = gradients_impl.gradients(loss, [inputs_t])[0] @@ -233,7 +233,7 @@ class CTCLossTest(test.TestCase): # Transposing tensor to [batch_size x max_time x depth tensor] inputs_t_transposed = constant_op.constant(inputs.transpose(1, 0, 2)) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: loss = ctc_ops.ctc_loss( inputs=inputs_t, labels=labels, sequence_length=seq_lens) loss_transposed = ctc_ops.ctc_loss( @@ -252,7 +252,7 @@ class CTCLossTest(test.TestCase): seq_lens = np.array([2, 2], dtype=np.int32) v = [1.0] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): loss = ctc_ops.ctc_loss( inputs=inputs_t, labels=labels, sequence_length=seq_lens) # Taking ths second gradient should fail, since it is not @@ -269,7 +269,7 @@ class CTCLossTest(test.TestCase): values=constant_op.constant([], shape=(0,), dtype=dtypes.int32), dense_shape=[5, 5]) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "batch_size must not be 0"): sess.run(ctc_ops.ctc_loss(labels, inputs, sequence_lengths)) diff --git a/tensorflow/python/kernel_tests/dct_ops_test.py b/tensorflow/python/kernel_tests/dct_ops_test.py index 97d7e2d8f9..c9d0167608 100644 --- a/tensorflow/python/kernel_tests/dct_ops_test.py +++ b/tensorflow/python/kernel_tests/dct_ops_test.py @@ -114,7 +114,7 @@ class DCTOpsTest(test.TestCase): def test_random(self): """Test randomly generated batches of data.""" with spectral_ops_test_util.fft_kernel_label_map(): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in ([1], [2], [3], [10], [2, 20], [2, 3, 25]): signals = np.random.rand(*shape).astype(np.float32) for norm in (None, "ortho"): diff --git a/tensorflow/python/kernel_tests/decode_image_op_test.py b/tensorflow/python/kernel_tests/decode_image_op_test.py index 7f73fbaa84..0975f964b5 100644 --- a/tensorflow/python/kernel_tests/decode_image_op_test.py +++ b/tensorflow/python/kernel_tests/decode_image_op_test.py @@ -36,7 +36,7 @@ class DecodeImageOpTest(test.TestCase): def testBmp(self): # Read a real bmp and verify shape path = os.path.join(prefix_path, "bmp", "testdata", "lena.bmp") - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: bmp0 = io_ops.read_file(path) image0 = image_ops.decode_image(bmp0) image1 = image_ops.decode_bmp(bmp0) @@ -52,7 +52,7 @@ class DecodeImageOpTest(test.TestCase): stride = 5 shape = (12, height, width, 3) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: gif0 = io_ops.read_file(path) image0 = image_ops.decode_image(gif0) image1 = image_ops.decode_gif(gif0) @@ -81,7 +81,7 @@ class DecodeImageOpTest(test.TestCase): def testJpeg(self): # Read a real jpeg and verify shape path = os.path.join(prefix_path, "jpeg", "testdata", "jpeg_merge_test1.jpg") - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: jpeg0 = io_ops.read_file(path) image0 = image_ops.decode_image(jpeg0) image1 = image_ops.decode_jpeg(jpeg0) @@ -99,7 +99,7 @@ class DecodeImageOpTest(test.TestCase): inputs = [(1, "lena_gray.png")] for channels_in, filename in inputs: for channels in 0, 1, 3, 4: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: path = os.path.join(prefix_path, "png", "testdata", filename) png0 = io_ops.read_file(path) image0 = image_ops.decode_image(png0, channels=channels) diff --git a/tensorflow/python/kernel_tests/denormal_test.py b/tensorflow/python/kernel_tests/denormal_test.py index 95fc40f883..71a528c4aa 100644 --- a/tensorflow/python/kernel_tests/denormal_test.py +++ b/tensorflow/python/kernel_tests/denormal_test.py @@ -39,7 +39,7 @@ class DenormalTest(test.TestCase): # Disabled denormal_test on power/s390x platform # Check relevant discussion - https://github.com/tensorflow/tensorflow/issues/11902 return - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): array_ops.identity(7).eval() for dtype in dtypes: tiny = np.finfo(dtype).tiny diff --git a/tensorflow/python/kernel_tests/dense_update_ops_test.py b/tensorflow/python/kernel_tests/dense_update_ops_test.py index 120e10314f..3e0a03d634 100644 --- a/tensorflow/python/kernel_tests/dense_update_ops_test.py +++ b/tensorflow/python/kernel_tests/dense_update_ops_test.py @@ -32,7 +32,7 @@ class AssignOpTest(test.TestCase): def _initAssignFetch(self, x, y, use_gpu=False): """Initialize a param to init and update it with y.""" super(AssignOpTest, self).setUp() - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): p = variables.Variable(x) assign = state_ops.assign(p, y) p.initializer.run() @@ -41,7 +41,7 @@ class AssignOpTest(test.TestCase): def _initAssignAddFetch(self, x, y, use_gpu=False): """Initialize a param to init, and compute param += y.""" - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): p = variables.Variable(x) add = state_ops.assign_add(p, y) p.initializer.run() @@ -50,7 +50,7 @@ class AssignOpTest(test.TestCase): def _initAssignSubFetch(self, x, y, use_gpu=False): """Initialize a param to init, and compute param -= y.""" - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): p = variables.Variable(x) sub = state_ops.assign_sub(p, y) p.initializer.run() diff --git a/tensorflow/python/kernel_tests/depthtospace_op_test.py b/tensorflow/python/kernel_tests/depthtospace_op_test.py index f0beabb4e2..13a28caf1f 100644 --- a/tensorflow/python/kernel_tests/depthtospace_op_test.py +++ b/tensorflow/python/kernel_tests/depthtospace_op_test.py @@ -37,12 +37,12 @@ class DepthToSpaceTest(test.TestCase): def _testOne(self, inputs, block_size, outputs, dtype=dtypes.float32): input_nhwc = math_ops.cast(inputs, dtype) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): # test NHWC (default) on CPU x_tf = array_ops.depth_to_space(input_nhwc, block_size) self.assertAllEqual(x_tf.eval(), outputs) if test.is_gpu_available(): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # test NHWC (default) on GPU x_tf = array_ops.depth_to_space(input_nhwc, block_size) self.assertAllEqual(x_tf.eval(), outputs) @@ -102,13 +102,13 @@ class DepthToSpaceTest(test.TestCase): input_nhwc = array_ops.ones([batch_size, 2, 3, 12]) x_out = array_ops.ones([batch_size, 4, 6, 3]) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): # test NHWC (default) on CPU x_tf = array_ops.depth_to_space(input_nhwc, block_size) self.assertAllEqual(x_tf.shape, x_out.shape) x_tf.eval() if test.is_gpu_available(): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # test NHWC (default) on GPU x_tf = array_ops.depth_to_space(input_nhwc, block_size) self.assertAllEqual(x_tf.shape, x_out.shape) @@ -276,7 +276,7 @@ class DepthToSpaceTest(test.TestCase): expected = self.depthToSpaceUsingTranspose(t, block_size, data_format) actual = array_ops.depth_to_space(t, block_size, data_format=data_format) - with self.test_session(use_gpu=use_gpu) as sess: + with self.session(use_gpu=use_gpu) as sess: actual_vals, expected_vals = sess.run([actual, expected]) self.assertTrue(np.array_equal(actual_vals, expected_vals)) @@ -314,7 +314,7 @@ class DepthToSpaceGradientTest(test.TestCase): return assert 4 == x.ndim - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_x = ops.convert_to_tensor(x) tf_y = array_ops.depth_to_space(tf_x, block_size, data_format=data_format) diff --git a/tensorflow/python/kernel_tests/depthwise_conv_op_test.py b/tensorflow/python/kernel_tests/depthwise_conv_op_test.py index 737a73f97a..77b27c6c7e 100644 --- a/tensorflow/python/kernel_tests/depthwise_conv_op_test.py +++ b/tensorflow/python/kernel_tests/depthwise_conv_op_test.py @@ -209,7 +209,7 @@ class DepthwiseConv2DTest(test.TestCase): # GitHub issue 22110. if not test.is_gpu_available(): return - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = array_ops.placeholder(dtypes.float32) f = np.ones([1, 1, 1, 1], np.float32) v = nn_impl.depthwise_conv2d( @@ -263,7 +263,7 @@ class DepthwiseConv2DTest(test.TestCase): # 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)] - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: t1 = constant_op.constant(x1, shape=tensor_in_sizes) t1.set_shape(tensor_in_sizes) t2 = constant_op.constant(x2, shape=filter_in_sizes) @@ -522,7 +522,7 @@ class DepthwiseConv2DTest(test.TestCase): x2 = np.random.rand(*output_sizes).astype(np.float32) def _GetVal(use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): t0 = constant_op.constant(input_sizes, shape=[len(input_sizes)]) t1 = constant_op.constant(x1, shape=filter_sizes) t2 = constant_op.constant(x2, shape=output_sizes) @@ -542,7 +542,7 @@ class DepthwiseConv2DTest(test.TestCase): x2 = np.random.rand(*output_sizes).astype(np.float64) def _GetVal(use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): t0 = constant_op.constant(input_sizes, shape=[len(input_sizes)]) t1 = constant_op.constant(x1, shape=filter_sizes) t2 = constant_op.constant(x2, shape=output_sizes) @@ -574,7 +574,7 @@ class DepthwiseConv2DTest(test.TestCase): x2 = np.random.rand(*output_sizes).astype(np.float32) def _GetVal(use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): t0 = constant_op.constant(x0, shape=input_sizes) t1 = constant_op.constant(filter_sizes, shape=[len(filter_sizes)]) t2 = constant_op.constant(x2, shape=output_sizes) @@ -594,7 +594,7 @@ class DepthwiseConv2DTest(test.TestCase): x2 = np.random.rand(*output_sizes).astype(np.float64) def _GetVal(use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): t0 = constant_op.constant(x0, shape=input_sizes) t1 = constant_op.constant(filter_sizes, shape=[len(filter_sizes)]) t2 = constant_op.constant(x2, shape=output_sizes) diff --git a/tensorflow/python/kernel_tests/determinant_op_test.py b/tensorflow/python/kernel_tests/determinant_op_test.py index fb114f9f24..da33b2848b 100644 --- a/tensorflow/python/kernel_tests/determinant_op_test.py +++ b/tensorflow/python/kernel_tests/determinant_op_test.py @@ -62,7 +62,7 @@ class DeterminantOpTest(test.TestCase): atol=5e-5) def _compareDeterminant(self, matrix_x): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): self._compareDeterminantBase(matrix_x, linalg_ops.matrix_determinant(matrix_x)) self._compareLogDeterminantBase( @@ -150,7 +150,7 @@ class DeterminantOpTest(test.TestCase): self._compareDeterminant(np.empty([2, 0, 0])) def testConcurrentExecutesWithoutError(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: matrix1 = random_ops.random_normal([5, 5], seed=42) matrix2 = random_ops.random_normal([5, 5], seed=42) det1 = linalg_ops.matrix_determinant(matrix1) diff --git a/tensorflow/python/kernel_tests/diag_op_test.py b/tensorflow/python/kernel_tests/diag_op_test.py index 0825d8fc6b..9e43258fa2 100644 --- a/tensorflow/python/kernel_tests/diag_op_test.py +++ b/tensorflow/python/kernel_tests/diag_op_test.py @@ -32,7 +32,7 @@ from tensorflow.python.platform import tf_logging class MatrixDiagTest(test.TestCase): def testVector(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v = np.array([1.0, 2.0, 3.0]) mat = np.diag(v) v_diag = array_ops.matrix_diag(v) @@ -40,7 +40,7 @@ class MatrixDiagTest(test.TestCase): self.assertAllEqual(v_diag.eval(), mat) def _testBatchVector(self, dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): v_batch = np.array([[1.0, 0.0, 3.0], [4.0, 5.0, 6.0]]).astype(dtype) mat_batch = np.array([[[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 3.0]], [[4.0, 0.0, 0.0], [0.0, 5.0, 0.0], @@ -61,14 +61,14 @@ class MatrixDiagTest(test.TestCase): array_ops.matrix_diag(0) def testInvalidShapeAtEval(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v = array_ops.placeholder(dtype=dtypes_lib.float32) with self.assertRaisesOpError("input must be at least 1-dim"): array_ops.matrix_diag(v).eval(feed_dict={v: 0.0}) def testGrad(self): shapes = ((3,), (7, 4)) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in shapes: x = constant_op.constant(np.random.rand(*shape), np.float32) y = array_ops.matrix_diag(x) @@ -82,7 +82,7 @@ class MatrixDiagTest(test.TestCase): class MatrixSetDiagTest(test.TestCase): def testSquare(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v = np.array([1.0, 2.0, 3.0]) mat = np.array([[0.0, 1.0, 0.0], [1.0, 0.0, 1.0], [1.0, 1.0, 1.0]]) mat_set_diag = np.array([[1.0, 1.0, 0.0], [1.0, 2.0, 1.0], @@ -92,7 +92,7 @@ class MatrixSetDiagTest(test.TestCase): self.assertAllEqual(mat_set_diag, output.eval()) def testRectangular(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v = np.array([3.0, 4.0]) mat = np.array([[0.0, 1.0, 0.0], [1.0, 0.0, 1.0]]) expected = np.array([[3.0, 1.0, 0.0], [1.0, 4.0, 1.0]]) @@ -108,7 +108,7 @@ class MatrixSetDiagTest(test.TestCase): self.assertAllEqual(expected, output.eval()) def _testSquareBatch(self, dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): v_batch = np.array([[-1.0, 0.0, -3.0], [-4.0, -5.0, -6.0]]).astype(dtype) mat_batch = np.array([[[1.0, 0.0, 3.0], [0.0, 2.0, 0.0], [1.0, 0.0, 3.0]], [[4.0, 0.0, 4.0], [0.0, 5.0, 0.0], @@ -131,7 +131,7 @@ class MatrixSetDiagTest(test.TestCase): self._testSquareBatch(np.bool) def testRectangularBatch(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v_batch = np.array([[-1.0, -2.0], [-4.0, -5.0]]) mat_batch = np.array([[[1.0, 0.0, 3.0], [0.0, 2.0, 0.0]], [[4.0, 0.0, 4.0], [0.0, 5.0, 0.0]]]) @@ -149,7 +149,7 @@ class MatrixSetDiagTest(test.TestCase): array_ops.matrix_set_diag([[0]], 0) def testInvalidShapeAtEval(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v = array_ops.placeholder(dtype=dtypes_lib.float32) with self.assertRaisesOpError("input must be at least 2-dim"): array_ops.matrix_set_diag(v, [v]).eval(feed_dict={v: 0.0}) @@ -159,7 +159,7 @@ class MatrixSetDiagTest(test.TestCase): def testGrad(self): shapes = ((3, 4, 4), (3, 3, 4), (3, 4, 3), (7, 4, 8, 8)) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in shapes: x = constant_op.constant( np.random.rand(*shape), dtype=dtypes_lib.float32) @@ -179,7 +179,7 @@ class MatrixSetDiagTest(test.TestCase): self.assertLess(error_x_diag, 1e-4) def testGradWithNoShapeInformation(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: v = array_ops.placeholder(dtype=dtypes_lib.float32) mat = array_ops.placeholder(dtype=dtypes_lib.float32) grad_input = array_ops.placeholder(dtype=dtypes_lib.float32) @@ -201,7 +201,7 @@ class MatrixSetDiagTest(test.TestCase): class MatrixDiagPartTest(test.TestCase): def testSquare(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v = np.array([1.0, 2.0, 3.0]) mat = np.diag(v) mat_diag = array_ops.matrix_diag_part(mat) @@ -209,7 +209,7 @@ class MatrixDiagPartTest(test.TestCase): self.assertAllEqual(mat_diag.eval(), v) def testRectangular(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): mat = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) mat_diag = array_ops.matrix_diag_part(mat) self.assertAllEqual(mat_diag.eval(), np.array([1.0, 5.0])) @@ -218,7 +218,7 @@ class MatrixDiagPartTest(test.TestCase): self.assertAllEqual(mat_diag.eval(), np.array([1.0, 4.0])) def _testSquareBatch(self, dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): v_batch = np.array([[1.0, 0.0, 3.0], [4.0, 5.0, 6.0]]).astype(dtype) mat_batch = np.array([[[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 3.0]], [[4.0, 0.0, 0.0], [0.0, 5.0, 0.0], @@ -236,7 +236,7 @@ class MatrixDiagPartTest(test.TestCase): self._testSquareBatch(np.bool) def testRectangularBatch(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v_batch = np.array([[1.0, 2.0], [4.0, 5.0]]) mat_batch = np.array([[[1.0, 0.0, 0.0], [0.0, 2.0, 0.0]], [[4.0, 0.0, 0.0], [0.0, 5.0, 0.0]]]) @@ -250,14 +250,14 @@ class MatrixDiagPartTest(test.TestCase): array_ops.matrix_diag_part(0) def testInvalidShapeAtEval(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v = array_ops.placeholder(dtype=dtypes_lib.float32) with self.assertRaisesOpError("input must be at least 2-dim"): array_ops.matrix_diag_part(v).eval(feed_dict={v: 0.0}) def testGrad(self): shapes = ((3, 3), (2, 3), (3, 2), (5, 3, 3)) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in shapes: x = constant_op.constant(np.random.rand(*shape), dtype=np.float32) y = array_ops.matrix_diag_part(x) @@ -271,7 +271,7 @@ class MatrixDiagPartTest(test.TestCase): class DiagTest(test.TestCase): def _diagOp(self, diag, dtype, expected_ans, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_ans = array_ops.diag(ops.convert_to_tensor(diag.astype(dtype))) out = tf_ans.eval() tf_ans_inv = array_ops.diag_part(expected_ans) @@ -418,7 +418,7 @@ class DiagPartOpTest(test.TestCase): np.random.seed(0) def _diagPartOp(self, tensor, dtype, expected_ans, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tensor = ops.convert_to_tensor(tensor.astype(dtype)) tf_ans_inv = array_ops.diag_part(tensor) inv_out = tf_ans_inv.eval() @@ -441,7 +441,7 @@ class DiagPartOpTest(test.TestCase): i = np.arange(3) expected_ans = x[i, i] for shape in None, (None, 3), (3, None): - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): t = ops.convert_to_tensor(x.astype(np.float32)) t.set_shape(shape) tf_ans = array_ops.diag_part(t) @@ -497,7 +497,7 @@ class DiagGradOpTest(test.TestCase): np.random.seed(0) shapes = ((3,), (3, 3), (3, 3, 3)) dtypes = (dtypes_lib.float32, dtypes_lib.float64) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): errors = [] for shape in shapes: for dtype in dtypes: @@ -517,7 +517,7 @@ class DiagGradPartOpTest(test.TestCase): np.random.seed(0) shapes = ((3, 3), (3, 3, 3, 3)) dtypes = (dtypes_lib.float32, dtypes_lib.float64) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): errors = [] for shape in shapes: for dtype in dtypes: diff --git a/tensorflow/python/kernel_tests/draw_bounding_box_op_test.py b/tensorflow/python/kernel_tests/draw_bounding_box_op_test.py index 4f5b854e6f..c655876280 100644 --- a/tensorflow/python/kernel_tests/draw_bounding_box_op_test.py +++ b/tensorflow/python/kernel_tests/draw_bounding_box_op_test.py @@ -86,7 +86,7 @@ class DrawBoundingBoxOpTest(test.TestCase): image = image_ops_impl.convert_image_dtype(image, dtypes.float32) image = array_ops.expand_dims(image, 0) image = image_ops.draw_bounding_boxes(image, bboxes) - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: op_drawn_image = np.squeeze(sess.run(image), 0) self.assertAllEqual(test_drawn_image, op_drawn_image) diff --git a/tensorflow/python/kernel_tests/dynamic_partition_op_test.py b/tensorflow/python/kernel_tests/dynamic_partition_op_test.py index 9557e30993..07da855a01 100644 --- a/tensorflow/python/kernel_tests/dynamic_partition_op_test.py +++ b/tensorflow/python/kernel_tests/dynamic_partition_op_test.py @@ -35,7 +35,7 @@ from tensorflow.python.platform import test class DynamicPartitionTest(test.TestCase): def testSimpleOneDimensional(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant([0, 13, 2, 39, 4, 17], dtype=dtypes.float32) indices = constant_op.constant([0, 0, 2, 3, 2, 1]) partitions = data_flow_ops.dynamic_partition( @@ -55,7 +55,7 @@ class DynamicPartitionTest(test.TestCase): self.assertEqual([None], partitions[3].get_shape().as_list()) def testSimpleTwoDimensional(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant([[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17]], dtype=dtypes.float32) @@ -82,7 +82,7 @@ class DynamicPartitionTest(test.TestCase): indices_list = [x % 2 for x in range(num)] part1 = [x for x in range(num) if x % 2 == 0] part2 = [x for x in range(num) if x % 2 == 1] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float32) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( @@ -104,7 +104,7 @@ class DynamicPartitionTest(test.TestCase): parts = [[] for _ in range(num_partitions)] for i in range(rows): parts[(i ** 2) % num_partitions].append(data_list[i]) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float32) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( @@ -120,7 +120,7 @@ class DynamicPartitionTest(test.TestCase): def testSimpleComplex(self): data_list = [1 + 2j, 3 + 4j, 5 + 6j, 7 + 8j] indices_list = [1, 0, 1, 0] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.complex64) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( @@ -133,7 +133,7 @@ class DynamicPartitionTest(test.TestCase): def testScalarPartitions(self): data_list = [10, 13, 12, 11] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float64) indices = 3 partitions = data_flow_ops.dynamic_partition( @@ -153,7 +153,7 @@ class DynamicPartitionTest(test.TestCase): def testHigherRank(self): np.random.seed(7) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: for n in 2, 3: for shape in (4,), (4, 5), (4, 5, 2): partitions = np.random.randint(n, size=np.prod(shape)).reshape(shape) @@ -178,7 +178,7 @@ class DynamicPartitionTest(test.TestCase): def testEmptyParts(self): data_list = [1, 2, 3, 4] indices_list = [1, 3, 1, 3] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float32) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( @@ -194,7 +194,7 @@ class DynamicPartitionTest(test.TestCase): def testEmptyDataTwoDimensional(self): data_list = [[], []] indices_list = [0, 1] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float32) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( @@ -210,7 +210,7 @@ class DynamicPartitionTest(test.TestCase): def testEmptyPartitions(self): data_list = [] indices_list = [] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float32) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( @@ -231,7 +231,7 @@ class DynamicPartitionTest(test.TestCase): data_list = [1, 2, 3, 4, 5, 6] indices_list = [6, 5, 4, 3, 1, 0] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float32) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( @@ -252,7 +252,7 @@ class DynamicPartitionTest(test.TestCase): data_list = [1, 2, 3, 4, 5, 6] indices_list = [10, 11, 2, 12, 0, 1000] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float32) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( @@ -276,7 +276,7 @@ class DynamicPartitionTest(test.TestCase): data_list = [1.1, 2.1, 3.1, 4.1, 5.1, 6.1] indices_list = [90, 70, 60, 100, 110, 40] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: data = constant_op.constant(data_list, dtype=dtypes.float32) indices = constant_op.constant(indices_list, dtype=dtypes.int32) partitions = data_flow_ops.dynamic_partition( diff --git a/tensorflow/python/kernel_tests/dynamic_stitch_op_test.py b/tensorflow/python/kernel_tests/dynamic_stitch_op_test.py index 61542528b8..c3f67d29aa 100644 --- a/tensorflow/python/kernel_tests/dynamic_stitch_op_test.py +++ b/tensorflow/python/kernel_tests/dynamic_stitch_op_test.py @@ -36,7 +36,7 @@ class DynamicStitchTestBase(object): self.stitch_op = stitch_op def testScalar(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = [constant_op.constant(0), constant_op.constant(1)] data = [constant_op.constant(40), constant_op.constant(60)] for step in -1, 1: @@ -47,7 +47,7 @@ class DynamicStitchTestBase(object): self.assertEqual([2], stitched_t.get_shape().as_list()) def testShapeInferenceForScalarWithNonConstantIndices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = [ array_ops.placeholder(dtype=dtypes.int32), constant_op.constant(1) @@ -61,7 +61,7 @@ class DynamicStitchTestBase(object): self.assertEqual([None], stitched_t.get_shape().as_list()) def testSimpleOneDimensional(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # Test various datatypes in the simple case to ensure that the op was # registered under those types. dtypes_to_test = [ @@ -84,7 +84,7 @@ class DynamicStitchTestBase(object): self.assertEqual([8], stitched_t.get_shape().as_list()) def testOneListOneDimensional(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = [constant_op.constant([1, 6, 2, 3, 5, 0, 4, 7])] data = [constant_op.constant([10, 60, 20, 30, 50, 0, 40, 70])] stitched_t = self.stitch_op(indices, data) @@ -94,7 +94,7 @@ class DynamicStitchTestBase(object): self.assertEqual([8], stitched_t.get_shape().as_list()) def testSimpleTwoDimensional(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = [ constant_op.constant([0, 4, 7]), constant_op.constant([1, 6]), @@ -113,7 +113,7 @@ class DynamicStitchTestBase(object): self.assertEqual([8, 2], stitched_t.get_shape().as_list()) def testZeroSizeTensor(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = [ constant_op.constant([0, 4, 7]), constant_op.constant([1, 6]), @@ -134,7 +134,7 @@ class DynamicStitchTestBase(object): self.assertEqual([8, 2], stitched_t.get_shape().as_list()) def testHigherRank(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: indices = [ constant_op.constant(6), constant_op.constant([4, 1]), @@ -222,7 +222,7 @@ class ParallelDynamicStitchTest(DynamicStitchTestBase, test.TestCase): DynamicStitchTestBase.__init__(self, data_flow_ops.parallel_dynamic_stitch) def testScalar(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = [constant_op.constant(0), constant_op.constant(1)] data = [constant_op.constant(40.0), constant_op.constant(60.0)] for step in -1, 1: @@ -233,7 +233,7 @@ class ParallelDynamicStitchTest(DynamicStitchTestBase, test.TestCase): self.assertEqual([2], stitched_t.get_shape().as_list()) def testHigherRank(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: indices = [ constant_op.constant(6), constant_op.constant([4, 1]), diff --git a/tensorflow/python/kernel_tests/edit_distance_op_test.py b/tensorflow/python/kernel_tests/edit_distance_op_test.py index 12f85af7a5..dab5eee7f5 100644 --- a/tensorflow/python/kernel_tests/edit_distance_op_test.py +++ b/tensorflow/python/kernel_tests/edit_distance_op_test.py @@ -68,7 +68,7 @@ class EditDistanceTest(test.TestCase): ] # SparseTensorValue inputs. - with ops.Graph().as_default() as g, self.test_session(g): + with ops.Graph().as_default() as g, self.session(g): # hypothesis and truth are (index, value, shape) tuples self._testEditDistanceST( hypothesis_st=sparse_tensor.SparseTensorValue( @@ -81,7 +81,7 @@ class EditDistanceTest(test.TestCase): expected_err_re=expected_err_re) # SparseTensor inputs. - with ops.Graph().as_default() as g, self.test_session(g): + with ops.Graph().as_default() as g, self.session(g): # hypothesis and truth are (index, value, shape) tuples self._testEditDistanceST( hypothesis_st=sparse_tensor.SparseTensor( diff --git a/tensorflow/python/kernel_tests/embedding_ops_test.py b/tensorflow/python/kernel_tests/embedding_ops_test.py index 40b8548cea..008d6fbf57 100644 --- a/tensorflow/python/kernel_tests/embedding_ops_test.py +++ b/tensorflow/python/kernel_tests/embedding_ops_test.py @@ -61,7 +61,7 @@ class ScatterAddSubTest(test.TestCase): scatter_op: ScatterAdd or ScatterSub. """ super(ScatterAddSubTest, self).setUp() - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): # Create a random parameter array of given shape p_init = np.random.rand(*shape).astype("f") # Create the shape of the update array. All dimensions except the last @@ -969,7 +969,7 @@ class SafeEmbeddingLookupSparseTest(test.TestCase): class DynamicStitchOpTest(test.TestCase): def testCint32Cpu(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = [ ops.convert_to_tensor([0, 1, 2]), ops.convert_to_tensor([2, 3]) @@ -982,7 +982,7 @@ class DynamicStitchOpTest(test.TestCase): data_flow_ops.dynamic_stitch(indices, values).eval(), [12, 23, 1, 2]) def testCint32Gpu(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = [ ops.convert_to_tensor([0, 1, 2]), ops.convert_to_tensor([2, 3]) @@ -995,7 +995,7 @@ class DynamicStitchOpTest(test.TestCase): data_flow_ops.dynamic_stitch(indices, values).eval(), [12, 23, 1, 2]) def testInt32Cpu(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = [ ops.convert_to_tensor([0, 1, 2]), ops.convert_to_tensor([2, 3]) @@ -1008,7 +1008,7 @@ class DynamicStitchOpTest(test.TestCase): data_flow_ops.dynamic_stitch(indices, values).eval(), [12, 23, 1, 2]) def testInt32Gpu(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = [ ops.convert_to_tensor([0, 1, 2]), ops.convert_to_tensor([2, 3]) @@ -1021,7 +1021,7 @@ class DynamicStitchOpTest(test.TestCase): data_flow_ops.dynamic_stitch(indices, values).eval(), [12, 23, 1, 2]) def testSumGradArgs(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = [ ops.convert_to_tensor([0, 1, 2, 3]), ops.convert_to_tensor([2, 3]) @@ -1050,7 +1050,7 @@ class DynamicStitchOpTest(test.TestCase): class ParallelDynamicStitchOpTest(test.TestCase): def testCint32Cpu(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = [ ops.convert_to_tensor([0, 1, 4, 6]), ops.convert_to_tensor([2, 3, 5]) @@ -1064,7 +1064,7 @@ class ParallelDynamicStitchOpTest(test.TestCase): [12, 23, 1, 2, 34, 3, 45]) def testInt32Cpu(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = [ ops.convert_to_tensor([0, 1, 5, 6, 7]), ops.convert_to_tensor([2, 4, 3]) @@ -1078,7 +1078,7 @@ class ParallelDynamicStitchOpTest(test.TestCase): [12, 23, 1, 2, 3, 34, 45, 56]) def testSimple(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = [ops.convert_to_tensor([0, 1]), ops.convert_to_tensor([2, 3])] values = [ops.convert_to_tensor([2, 3]), ops.convert_to_tensor([1, 1])] self.assertAllEqual( diff --git a/tensorflow/python/kernel_tests/extract_image_patches_op_test.py b/tensorflow/python/kernel_tests/extract_image_patches_op_test.py index 6ea9f1badc..61436f24cf 100644 --- a/tensorflow/python/kernel_tests/extract_image_patches_op_test.py +++ b/tensorflow/python/kernel_tests/extract_image_patches_op_test.py @@ -43,7 +43,7 @@ class ExtractImagePatches(test.TestCase): strides = [1] + strides + [1] rates = [1] + rates + [1] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): out_tensor = array_ops.extract_image_patches( constant_op.constant(image), ksizes=ksizes, diff --git a/tensorflow/python/kernel_tests/extract_volume_patches_op_test.py b/tensorflow/python/kernel_tests/extract_volume_patches_op_test.py index 64757a3e07..bbb3fef85b 100644 --- a/tensorflow/python/kernel_tests/extract_volume_patches_op_test.py +++ b/tensorflow/python/kernel_tests/extract_volume_patches_op_test.py @@ -45,7 +45,7 @@ class ExtractVolumePatches(test.TestCase): ksizes = [1] + ksizes + [1] strides = [1] + strides + [1] - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): out_tensor = array_ops.extract_volume_patches( constant_op.constant(image), ksizes=ksizes, diff --git a/tensorflow/python/kernel_tests/fft_ops_test.py b/tensorflow/python/kernel_tests/fft_ops_test.py index f117934e4b..8592550f99 100644 --- a/tensorflow/python/kernel_tests/fft_ops_test.py +++ b/tensorflow/python/kernel_tests/fft_ops_test.py @@ -68,12 +68,12 @@ class BaseFFTOpsTest(test.TestCase): def _checkMemoryFail(self, x, rank): config = config_pb2.ConfigProto() config.gpu_options.per_process_gpu_memory_fraction = 1e-2 - with self.test_session(config=config, force_gpu=True): + with self.cached_session(config=config, force_gpu=True): self._tfFFT(x, rank, fft_length=None) def _checkGradComplex(self, func, x, y, result_is_complex=True, rtol=1e-2, atol=1e-2): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(x) iny = ops.convert_to_tensor(y) # func is a forward or inverse, real or complex, batched or unbatched FFT @@ -93,7 +93,7 @@ class BaseFFTOpsTest(test.TestCase): self.assertAllClose(y_jacob_t, y_jacob_n, rtol=rtol, atol=atol) def _checkGradReal(self, func, x, rtol=1e-2, atol=1e-2): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(x) # func is a forward RFFT function (batched or unbatched). z = func(inx) @@ -109,12 +109,12 @@ class FFTOpsTest(BaseFFTOpsTest): def _tfFFT(self, x, rank, fft_length=None, feed_dict=None): # fft_length unused for complex FFTs. - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): return self._tfFFTForRank(rank)(x).eval(feed_dict=feed_dict) def _tfIFFT(self, x, rank, fft_length=None, feed_dict=None): # fft_length unused for complex FFTs. - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): return self._tfIFFTForRank(rank)(x).eval(feed_dict=feed_dict) def _npFFT(self, x, rank, fft_length=None): @@ -283,11 +283,11 @@ class RFFTOpsTest(BaseFFTOpsTest): use_placeholder) def _tfFFT(self, x, rank, fft_length=None, feed_dict=None): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): return self._tfFFTForRank(rank)(x, fft_length).eval(feed_dict=feed_dict) def _tfIFFT(self, x, rank, fft_length=None, feed_dict=None): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): return self._tfIFFTForRank(rank)(x, fft_length).eval(feed_dict=feed_dict) def _npFFT(self, x, rank, fft_length=None): diff --git a/tensorflow/python/kernel_tests/fifo_queue_test.py b/tensorflow/python/kernel_tests/fifo_queue_test.py index a5f8f64e0c..8961c4b13c 100644 --- a/tensorflow/python/kernel_tests/fifo_queue_test.py +++ b/tensorflow/python/kernel_tests/fifo_queue_test.py @@ -1586,7 +1586,7 @@ class FIFOQueueDictTest(test.TestCase): class FIFOQueueWithTimeoutTest(test.TestCase): def testDequeueWithTimeout(self): - with self.test_session( + with self.session( config=config_pb2.ConfigProto(operation_timeout_in_ms=20)) as sess: q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) self.assertEqual( diff --git a/tensorflow/python/kernel_tests/gather_nd_op_test.py b/tensorflow/python/kernel_tests/gather_nd_op_test.py index c0b419e1d1..706a4e27e5 100644 --- a/tensorflow/python/kernel_tests/gather_nd_op_test.py +++ b/tensorflow/python/kernel_tests/gather_nd_op_test.py @@ -35,7 +35,7 @@ from tensorflow.python.platform import test class GatherNdTest(test.TestCase): def _testSimpleDtype(self, dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): params = constant_op.constant(np.array([8, 1, 2, 3, 7, 5], dtype=dtype)) indices = constant_op.constant([[4], [4], [0]]) gather_nd_t = array_ops.gather_nd(params, indices) @@ -54,7 +54,7 @@ class GatherNdTest(test.TestCase): self._testSimpleDtype("|S") # byte strings in python2 + 3 def testEmptyIndicesAndParamsOKButJustEmptyParamsFails(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = np.ones((3, 3), dtype=np.float32) indices_empty = np.empty((0, 2), dtype=np.int32) @@ -85,7 +85,7 @@ class GatherNdTest(test.TestCase): self.assertAllClose(np.empty((0,), dtype=np.float32), gather_nd_ok_val) def testIndexScalar(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = np.array( [[-8, -1, -2, -3, -7, -5], [8, 1, 2, 3, 7, 5]], dtype=np.float32).T indices = constant_op.constant([4, 1]) @@ -95,7 +95,7 @@ class GatherNdTest(test.TestCase): self.assertAllEqual(np.array(7), gather_nd_val) def testParamsRankLargerThanIndexIndexScalarSlices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = np.array( [[-8, -1, -2, -3, -7, -5], [8, 1, 2, 3, 7, 5]], dtype=np.float32).T indices = constant_op.constant([4]) @@ -105,7 +105,7 @@ class GatherNdTest(test.TestCase): self.assertAllEqual(np.array([-7, 7]), gather_nd_val) def testParamsRankLargerThanIndexSlices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = np.array( [[-8, -1, -2, -3, -7, -5], [8, 1, 2, 3, 7, 5]], dtype=np.float32).T indices = constant_op.constant([[4], [4], [0]]) @@ -116,7 +116,7 @@ class GatherNdTest(test.TestCase): self.assertAllEqual(np.array([[-7, 7], [-7, 7], [-8, 8]]), gather_nd_val) def testHigherRankParamsLargerThanIndexSlices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = np.array( [[[-8, -1, -2, -3, -7, -5], [8, 1, 2, 3, 7, 5]], [[-80, -10, -20, -30, -70, -50], [80, 10, 20, 30, 70, 50]]], @@ -130,7 +130,7 @@ class GatherNdTest(test.TestCase): self.assertAllEqual(params[[4, 4, 0]], gather_nd_val) def testEmptyIndicesLastRankMeansCopyEntireTensor(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = np.array( [[[-8, -1, -2, -3, -7, -5], [8, 1, 2, 3, 7, 5]], [[-80, -10, -20, -30, -70, -50], [80, 10, 20, 30, 70, 50]]], @@ -147,7 +147,7 @@ class GatherNdTest(test.TestCase): gather_nd_val) def testHigherRankParamsAndIndicesLargerThanIndexSlices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = np.array( [[[-8, -1, -2, -3, -7, -5], [8, 1, 2, 3, 7, 5]], [[-80, -10, -20, -30, -70, -50], [80, 10, 20, 30, 70, 50]]], @@ -162,7 +162,7 @@ class GatherNdTest(test.TestCase): gather_nd_val) def testHigherRankParams(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = (10, 20, 5, 1, 17) params = np.random.rand(*shape) indices = np.vstack([np.random.randint(0, s, size=2000) for s in shape]).T @@ -174,7 +174,7 @@ class GatherNdTest(test.TestCase): self.assertEqual([2000], gather_nd_t.get_shape()) def testHigherRankParamsAndIndices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = (10, 20, 5, 1, 17) params = np.random.rand(*shape) indices = np.vstack([np.random.randint(0, s, size=2000) for s in shape]).T @@ -198,7 +198,7 @@ class GatherNdTest(test.TestCase): self.assertEqual(None, shape[0].value) def testBadIndicesCPU(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): params = [0, 1, 2] indices = [[[0], [7]]] # Make this one higher rank gather_nd = array_ops.gather_nd(params, indices) @@ -211,7 +211,7 @@ class GatherNdTest(test.TestCase): # On GPU the bad indices do not raise error but fetch 0 values if not test.is_gpu_available(): return - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = [0, 1, 2] indices = [[[0], [7]]] # Make this one higher rank gather_nd = array_ops.gather_nd(params, indices) @@ -220,7 +220,7 @@ class GatherNdTest(test.TestCase): gather_nd.eval() def testBadIndicesWithSlicesCPU(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): params = [[0, 1, 2]] indices = [[[0], [0], [1]]] # Make this one higher rank gather_nd = array_ops.gather_nd(params, indices) @@ -233,7 +233,7 @@ class GatherNdTest(test.TestCase): # On GPU the bad indices do not raise error but fetch 0 values if not test.is_gpu_available(): return - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = [[0, 1, 2]] indices = [[[0], [0], [1]]] # Make this one higher rank gather_nd = array_ops.gather_nd(params, indices) @@ -249,7 +249,7 @@ class GatherNdTest(test.TestCase): grad_vals = constant_op.constant([1, 2], dtype=dtypes.float64) grads = gradients_impl.gradients([outputs], [inputs], [grad_vals])[0] expected_grads = np.array([[1, 0], [0, 2]], dtype=np.float64) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): assert np.array_equal(expected_grads, grads.eval()) def testGradientsRank2Slices(self): @@ -260,7 +260,7 @@ class GatherNdTest(test.TestCase): grad_vals = constant_op.constant([[1, 2], [3, 4]], dtype=dtypes.float64) grads = gradients_impl.gradients([outputs], [inputs], [grad_vals])[0] expected_grads = np.array([[3, 4], [1, 2]], dtype=np.float64) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertIndexedSlices(grads) self.assertAllEqual(expected_grads, ops.convert_to_tensor(grads).eval()) @@ -276,7 +276,7 @@ class GatherNdTest(test.TestCase): grads = gradients_impl.gradients([outputs], [inputs], [grad_vals])[0] expected_grads = np.array( [[[5, 6], [1, 2]], [[3, 4], [7, 8]]], dtype=np.float64) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual(expected_grads, grads.eval()) def testGradientsRank7Elements(self): @@ -305,7 +305,7 @@ class GatherNdTest(test.TestCase): [[[[5, 6], [1, 2]]]], [[[[3, 4], [7, 8]]]] ]]], dtype=np.float64) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual(expected_grads, grads.eval()) def testGradientsInt64Indices(self): @@ -320,7 +320,7 @@ class GatherNdTest(test.TestCase): grads = gradients_impl.gradients([outputs], [inputs], [grad_vals])[0] expected_grads = np.array( [[[5, 6], [1, 2]], [[3, 4], [7, 8]]], dtype=np.float64) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual(expected_grads, grads.eval()) def testGradientsRank2SlicesWithEmptySpace(self): @@ -341,7 +341,7 @@ class GatherNdTest(test.TestCase): [1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0], [3, 3, 3, 3, 3, 3, 3, 3, 3]], dtype=np.float64) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertIndexedSlices(grads) self.assertAllEqual(expected_grads, ops.convert_to_tensor(grads).eval()) diff --git a/tensorflow/python/kernel_tests/gather_op_test.py b/tensorflow/python/kernel_tests/gather_op_test.py index 85bf969068..bdafc52ab5 100644 --- a/tensorflow/python/kernel_tests/gather_op_test.py +++ b/tensorflow/python/kernel_tests/gather_op_test.py @@ -42,7 +42,7 @@ class GatherTest(test.TestCase): return data def testScalar1D(self): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): data = np.array([0, 1, 2, 3, 7, 5]) for dtype in _TEST_TYPES: for indices in 4, [1, 2, 2, 4, 5]: @@ -56,7 +56,7 @@ class GatherTest(test.TestCase): self.assertEqual(np_val.shape, gather_t.get_shape()) def testScalar2D(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): data = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]]) for dtype in _TEST_TYPES: @@ -71,7 +71,7 @@ class GatherTest(test.TestCase): self.assertEqual(expected_shape, gather_t.get_shape()) def testSimpleTwoD32(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): data = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]]) for dtype in _TEST_TYPES: @@ -95,7 +95,7 @@ class GatherTest(test.TestCase): for axis in range(len(shape)): params = self._buildParams(np.random.randn(*shape), dtype) indices = np.random.randint(shape[axis], size=indices_shape) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: tf_params = constant_op.constant(params) tf_indices = constant_op.constant(indices) # Check that both positive and negative indices for axis work. @@ -182,7 +182,7 @@ class GatherTest(test.TestCase): self.assertEqual(None, gather_t.shape) def testBadIndicesCPU(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): params = [[0, 1, 2], [3, 4, 5]] with self.assertRaisesOpError(r"indices\[0,0\] = 7 is not in \[0, 2\)"): array_ops.gather(params, [[7]], axis=0).eval() @@ -194,7 +194,7 @@ class GatherTest(test.TestCase): # On GPU the bad indices do not raise error but fetch 0 values if not test.is_gpu_available(): return - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = [[0, 1, 2], [3, 4, 5]] with self.assertRaisesOpError(r"indices\[0,0\] = 7 is not in \[0, 2\)"): array_ops.gather(params, [[7]], axis=0).eval() @@ -202,7 +202,7 @@ class GatherTest(test.TestCase): array_ops.gather(params, [[7]], axis=1).eval() def testBadAxis(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): params = [0, 1, 2] params_ph = array_ops.placeholder(dtypes.int32) indices = 0 @@ -218,7 +218,7 @@ class GatherTest(test.TestCase): feed_dict={params_ph: params}) def testEmptySlices(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in _TEST_TYPES: for itype in np.int32, np.int64: # Leading axis gather. diff --git a/tensorflow/python/kernel_tests/init_ops_test.py b/tensorflow/python/kernel_tests/init_ops_test.py index 292679e4b9..70bfbf8544 100644 --- a/tensorflow/python/kernel_tests/init_ops_test.py +++ b/tensorflow/python/kernel_tests/init_ops_test.py @@ -107,7 +107,7 @@ def _init_sampler(tc, init, num): class ConstantInitializersTest(test.TestCase): def testZerosInitializer(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = [2, 3] x = variable_scope.get_variable( "x", shape=shape, initializer=init_ops.zeros_initializer()) @@ -115,7 +115,7 @@ class ConstantInitializersTest(test.TestCase): self.assertAllEqual(x.eval(), np.zeros(shape)) def testOnesInitializer(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = [2, 3] x = variable_scope.get_variable( "x", shape=shape, initializer=init_ops.ones_initializer()) @@ -123,7 +123,7 @@ class ConstantInitializersTest(test.TestCase): self.assertAllEqual(x.eval(), np.ones(shape)) def testConstantZeroInitializer(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = [2, 3] x = variable_scope.get_variable( "x", shape=shape, initializer=init_ops.constant_initializer(0.0)) @@ -131,7 +131,7 @@ class ConstantInitializersTest(test.TestCase): self.assertAllEqual(x.eval(), np.zeros(shape)) def testConstantOneInitializer(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = [2, 3] x = variable_scope.get_variable( "x", shape=shape, initializer=init_ops.constant_initializer(1.0)) @@ -139,7 +139,7 @@ class ConstantInitializersTest(test.TestCase): self.assertAllEqual(x.eval(), np.ones(shape)) def testConstantIntInitializer(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = [2, 3] x = variable_scope.get_variable( "x", @@ -151,7 +151,7 @@ class ConstantInitializersTest(test.TestCase): self.assertAllEqual(x.eval(), 7 * np.ones(shape, dtype=np.int32)) def testConstantTupleInitializer(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = [3] x = variable_scope.get_variable( "x", @@ -163,7 +163,7 @@ class ConstantInitializersTest(test.TestCase): self.assertAllEqual(x.eval(), [10, 20, 30]) def _testNDimConstantInitializer(self, name, value, shape, expected): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): init = init_ops.constant_initializer(value, dtype=dtypes.int32) x = variable_scope.get_variable(name, shape=shape, initializer=init) x.initializer.run() @@ -187,7 +187,7 @@ class ConstantInitializersTest(test.TestCase): def _testNDimConstantInitializerLessValues(self, name, value, shape, expected): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): init = init_ops.constant_initializer(value, dtype=dtypes.int32) x = variable_scope.get_variable(name, shape=shape, initializer=init) x.initializer.run() @@ -213,7 +213,7 @@ class ConstantInitializersTest(test.TestCase): def _testNDimConstantInitializerMoreValues(self, value, shape): ops.reset_default_graph() - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): init = init_ops.constant_initializer(value, dtype=dtypes.int32) self.assertRaises( ValueError, @@ -371,7 +371,7 @@ class VarianceScalingInitializationTest(test.TestCase): init = init_ops.variance_scaling_initializer( distribution='truncated_normal') - with self.test_session(use_gpu=True), \ + with self.session(use_gpu=True), \ test.mock.patch.object( random_ops, 'truncated_normal', wraps=random_ops.truncated_normal) \ as mock_truncated_normal: @@ -387,7 +387,7 @@ class VarianceScalingInitializationTest(test.TestCase): expect_var = 1. / shape[0] init = init_ops.variance_scaling_initializer(distribution='normal') - with self.test_session(use_gpu=True), \ + with self.session(use_gpu=True), \ test.mock.patch.object( random_ops, 'truncated_normal', wraps=random_ops.truncated_normal) \ as mock_truncated_normal: @@ -404,7 +404,7 @@ class VarianceScalingInitializationTest(test.TestCase): init = init_ops.variance_scaling_initializer( distribution='untruncated_normal') - with self.test_session(use_gpu=True), \ + with self.session(use_gpu=True), \ test.mock.patch.object( random_ops, 'random_normal', wraps=random_ops.random_normal) \ as mock_random_normal: @@ -420,7 +420,7 @@ class VarianceScalingInitializationTest(test.TestCase): expect_var = 1. / shape[0] init = init_ops.variance_scaling_initializer(distribution='uniform') - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = init(shape).eval() self.assertNear(np.mean(x), expect_mean, err=1e-2) @@ -431,7 +431,7 @@ class VarianceScalingInitializationTest(test.TestCase): class RangeTest(test.TestCase): def _Range(self, start, limit, delta): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_ans = math_ops.range(start, limit, delta, name="range") self.assertEqual([len(np.arange(start, limit, delta))], tf_ans.get_shape()) @@ -450,7 +450,7 @@ class RangeTest(test.TestCase): self.assertEqual(math_ops.range(0, 5, 1).dtype, dtypes.int32) def testLimitOnly(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual(np.arange(5), math_ops.range(5).eval()) def testEmpty(self): @@ -520,7 +520,6 @@ class LinSpaceTest(test.TestCase): return [False] def _LinSpace(self, start, stop, num): - # NOTE(touts): Needs to pass a graph to get a new session each time. with ops.Graph().as_default() as graph: with self.session(graph=graph, force_gpu=self.force_gpu): tf_ans = math_ops.linspace(start, stop, num, name="linspace") @@ -704,7 +703,7 @@ class ConvolutionDeltaOrthogonalInitializerTest(test.TestCase): outputs_2norm = linalg_ops.norm(outputs) ratio = outputs_2norm / inputs_2norm my_ops = variables.global_variables_initializer() - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run(my_ops) # Check the shape of the outputs t = outputs.eval() @@ -719,7 +718,7 @@ class ConvolutionDeltaOrthogonalInitializerTest(test.TestCase): shape = [3, 3, 10, 10] count = 70 tol = 1e-5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for i in range(count): x = variable_scope.get_variable("{}".format(i), shape=shape, initializer= @@ -783,7 +782,7 @@ class ConvolutionOrthogonal1dInitializerTest(test.TestCase): shape = [3, 10, 10] count = 70 tol = 1e-5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for i in range(count): x = variable_scope.get_variable("{}".format(i), shape=shape, initializer= @@ -843,7 +842,7 @@ class ConvolutionOrthogonal1dInitializerTest(test.TestCase): outputs_2norm = linalg_ops.norm(outputs) ratio = outputs_2norm / inputs_2norm my_ops = variables.global_variables_initializer() - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run(my_ops) # Check the shape of the outputs t = outputs.eval() @@ -938,7 +937,7 @@ class ConvolutionOrthogonal2dInitializerTest(test.TestCase): outputs_2norm = linalg_ops.norm(outputs) ratio = outputs_2norm / inputs_2norm my_ops = variables.global_variables_initializer() - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run(my_ops) # Check the shape of the outputs t = outputs.eval() @@ -992,7 +991,7 @@ class ConvolutionOrthogonal3dInitializerTest(test.TestCase): shape = [3, 3, 3, 5, 5] count = 20 tol = 1e-5 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for i in range(count): x = variable_scope.get_variable("{}".format(i), shape=shape, initializer= @@ -1063,7 +1062,7 @@ class ConvolutionOrthogonal3dInitializerTest(test.TestCase): outputs_2norm = linalg_ops.norm(outputs) ratio = outputs_2norm / inputs_2norm my_ops = variables.global_variables_initializer() - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: sess.run(my_ops) # Check the shape of the outputs t = outputs.eval() diff --git a/tensorflow/python/kernel_tests/inplace_ops_test.py b/tensorflow/python/kernel_tests/inplace_ops_test.py index 90759c23ae..51d16861dd 100644 --- a/tensorflow/python/kernel_tests/inplace_ops_test.py +++ b/tensorflow/python/kernel_tests/inplace_ops_test.py @@ -33,7 +33,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): def testBasicUpdate(self): for dtype in [dtypes.float32, dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = array_ops.ones([7, 3], dtype) y = np.ones([7, 3], dtype.as_numpy_dtype) self.assertAllClose(x.eval(), y) @@ -49,7 +49,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): self.assertAllClose(x.eval(), y) def testBasicUpdateBool(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = array_ops.ones([7, 3], dtypes.bool) y = np.ones([7, 3], dtypes.bool.as_numpy_dtype) self.assertAllClose(x.eval(), y) @@ -67,7 +67,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): def testBasicAdd(self): for dtype in [dtypes.float32, dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = array_ops.ones([7, 3], dtype) y = np.ones([7, 3], dtype.as_numpy_dtype) self.assertAllClose(x.eval(), y) @@ -86,7 +86,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): def testBasicSub(self): for dtype in [dtypes.float32, dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = array_ops.ones([7, 3], dtype) y = np.ones([7, 3], dtype.as_numpy_dtype) self.assertAllClose(x.eval(), y) @@ -104,7 +104,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): self.assertAllClose(x.eval(), y) def testRandom(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): d0, d1, d2 = 100, 3, 5 x = array_ops.zeros([d0, d1, d2]) y = np.zeros([d0, d1, d2]) @@ -124,7 +124,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): self.assertAllClose(x.eval(), y) def testRandom1D(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): d0 = 100 x = array_ops.zeros([d0]) y = np.zeros([d0]) @@ -144,7 +144,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): self.assertAllClose(x.eval(), y) def testAlias(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: x = array_ops.ones([2, 3]) y = inplace_ops.alias_inplace_add(x, [0], [[1, 2, 3]]) with ops.control_dependencies([y]): @@ -169,7 +169,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): dtypes.float32, dtypes.float64, dtypes.int32, dtypes.int64, dtypes.bool, dtypes.uint8 ]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): test_shapes = [(), (1,), (2, 3), (0, 2), (2, 3, 5), (2, 0, 5)] for shape in test_shapes: val = inplace_ops.empty(shape, dtype).eval() @@ -188,7 +188,7 @@ class InplaceOpsTest(test_util.TensorFlowTestCase): self.assertEqual(val.dtype, dtype.as_numpy_dtype) self.assertAllEqual(val, np.zeros(shape, dtype.as_numpy_dtype)) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): val = inplace_ops.empty((1, 2), dtypes.string, init=True).eval() self.assertEqual(val.tolist(), [[b"", b""]]) diff --git a/tensorflow/python/kernel_tests/large_concat_op_test.py b/tensorflow/python/kernel_tests/large_concat_op_test.py index 66afb6ec01..1b23e74776 100644 --- a/tensorflow/python/kernel_tests/large_concat_op_test.py +++ b/tensorflow/python/kernel_tests/large_concat_op_test.py @@ -32,7 +32,7 @@ class LargeConcatOpTest(test.TestCase): a = array_ops.ones([2**31 + 6], dtype=dtypes.int8) b = array_ops.zeros([1024], dtype=dtypes.int8) onezeros = array_ops.concat([a, b], 0) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): # TODO(dga): Add more depth to this test to validate correctness, # not just non-crashingness, once other large tensor fixes have gone in. _ = onezeros.eval() diff --git a/tensorflow/python/kernel_tests/linalg_grad_test.py b/tensorflow/python/kernel_tests/linalg_grad_test.py index e52f303fe0..bd78c484ea 100644 --- a/tensorflow/python/kernel_tests/linalg_grad_test.py +++ b/tensorflow/python/kernel_tests/linalg_grad_test.py @@ -60,7 +60,7 @@ class MatrixUnaryFunctorGradientTest(test_lib.TestCase): def _GetMatrixUnaryFunctorGradientTest(functor_, dtype_, shape_, **kwargs_): def Test(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): np.random.seed(1) a_np = np.random.uniform( low=-1.0, high=1.0, @@ -102,7 +102,7 @@ def _GetMatrixBinaryFunctorGradientTest(functor_, # GPU test for matrix_solve. use_gpu = False if functor_ == linalg_ops.matrix_solve else True - with self.test_session(use_gpu=use_gpu): + with self.session(use_gpu=use_gpu): np.random.seed(1) a_np = np.random.uniform( low=-1.0, high=1.0, diff --git a/tensorflow/python/kernel_tests/losses_test.py b/tensorflow/python/kernel_tests/losses_test.py index fb0b5f1137..b04996f788 100644 --- a/tensorflow/python/kernel_tests/losses_test.py +++ b/tensorflow/python/kernel_tests/losses_test.py @@ -1348,7 +1348,7 @@ class ComputeWeightedLossTest(test.TestCase): raw_losses, weights=np.ones(self._shape), reduction=reduction) ) self.assertEqual(9, len(util.get_losses())) - with self.test_session(g): + with self.session(g): for unweighted_loss in unweighted_losses: if reduction == losses.Reduction.NONE: self.assertAllClose(self._raw_losses, unweighted_loss.eval()) @@ -1375,7 +1375,7 @@ class ComputeWeightedLossTest(test.TestCase): raw_losses, weights=np.ones((1, 1, 4)), reduction=reduction), ) self.assertEqual(3, len(util.get_losses())) - with self.test_session(g): + with self.session(g): for unweighted_loss in unweighted_losses: if reduction == losses.Reduction.NONE: self.assertAllClose( @@ -1466,7 +1466,7 @@ class ComputeWeightedLossTest(test.TestCase): weighted_loss = losses.compute_weighted_loss( self._raw_losses, weights=weights, reduction=reduction) self.assertEqual(1, len(util.get_losses())) - with self.test_session(g): + with self.session(g): weighted_losses = weights * self._raw_losses weighted_sum = np.sum(weighted_losses) if reduction == losses.Reduction.NONE: diff --git a/tensorflow/python/kernel_tests/lrn_op_test.py b/tensorflow/python/kernel_tests/lrn_op_test.py index 9eba059549..7ebeb91d90 100644 --- a/tensorflow/python/kernel_tests/lrn_op_test.py +++ b/tensorflow/python/kernel_tests/lrn_op_test.py @@ -54,7 +54,7 @@ class LRNOpTest(test.TestCase): return output def _RunAndVerify(self, dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # random shape shape = np.random.randint(1, 16, size=4) # Make depth at least 2 to make it meaningful @@ -100,7 +100,7 @@ class LRNOpTest(test.TestCase): self._RunAndVerify(dtypes.float16) def testGradientsZeroInput(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): shape = [4, 4, 4, 4] p = array_ops.placeholder(dtypes.float32, shape=shape) inp_array = np.zeros(shape).astype("f") @@ -113,7 +113,7 @@ class LRNOpTest(test.TestCase): self.assertShapeEqual(expected, grad) def _RunAndVerifyGradients(self, dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # random shape shape = np.random.randint(1, 5, size=4) # Make depth at least 2 to make it meaningful diff --git a/tensorflow/python/kernel_tests/map_stage_op_test.py b/tensorflow/python/kernel_tests/map_stage_op_test.py index acfafde9e0..d503f3d7c9 100644 --- a/tensorflow/python/kernel_tests/map_stage_op_test.py +++ b/tensorflow/python/kernel_tests/map_stage_op_test.py @@ -44,7 +44,7 @@ class MapStageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: sess.run(stage, feed_dict={x: -1, pi: 0}) for i in range(10): _, yval = sess.run([stage, y], feed_dict={x: i, pi: i + 1, gi: i}) @@ -65,7 +65,7 @@ class MapStageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: sess.run(stage, feed_dict={x: -1, pi: 0}) for i in range(10): _, yval = sess.run([stage, y], feed_dict={x: i, pi: i + 1, gi: i}) @@ -92,7 +92,7 @@ class MapStageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: sess.run(stage, feed_dict={x: -1, pi: 0}) for i in range(10): _, yval = sess.run([stage, y], feed_dict={x: i, pi: i + 1, gi: i}) @@ -141,7 +141,7 @@ class MapStageTest(test.TestCase): n = 10 - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: for i in range(n): sess.run(stage, feed_dict={x: i, pi: i}) @@ -168,7 +168,7 @@ class MapStageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: sess.run(stage, feed_dict={x: -1, pi: 3}) self.assertEqual(sess.run(size), 1) sess.run(stage, feed_dict={x: -1, pi: 1}) @@ -202,7 +202,7 @@ class MapStageTest(test.TestCase): queue = Queue.Queue() n = 8 - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # Stage data in a separate thread which will block # when it hits the staging area's capacity and thus # not fill the queue with n tokens @@ -265,7 +265,7 @@ class MapStageTest(test.TestCase): queue = Queue.Queue() n = 8 - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # Stage data in a separate thread which will block # when it hits the staging area's capacity and thus # not fill the queue with n tokens @@ -325,7 +325,7 @@ class MapStageTest(test.TestCase): n = 10 - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # Keys n-1..0 keys = list(reversed(six.moves.range(n))) @@ -362,7 +362,7 @@ class MapStageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # 0 complete and incomplete entries self.assertTrue(sess.run([size, isize]) == [0, 0]) # Stage key 0, x and f tuple entries @@ -419,7 +419,7 @@ class MapStageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # 0 complete and incomplete entries self.assertTrue(sess.run([size, isize]) == [0, 0]) # Stage key 0, x and f tuple entries @@ -470,7 +470,7 @@ class MapStageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # 0 complete and incomplete entries self.assertTrue(sess.run([size, isize]) == [0, 0]) # Stage key 0, x and f tuple entries @@ -561,7 +561,7 @@ class MapStageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # Stage complete tuple sess.run(stage_xvf, feed_dict={pi: 0, x: 1, f: 2, v: 3}) diff --git a/tensorflow/python/kernel_tests/matmul_op_test.py b/tensorflow/python/kernel_tests/matmul_op_test.py index 01c4643235..4760236ca0 100644 --- a/tensorflow/python/kernel_tests/matmul_op_test.py +++ b/tensorflow/python/kernel_tests/matmul_op_test.py @@ -72,7 +72,7 @@ def _GetMatMulTest(a_np_, b_np_, use_static_shape_, **kwargs_): # np.matrix(a_np_) * np.matrix(b_np_) effective_a_np = _GetTransposedMatrices(a_np_, "a", kwargs_) effective_b_np = _GetTransposedMatrices(b_np_, "b", kwargs_) - with self.test_session(use_gpu=use_gpu) as sess: + with self.session(use_gpu=use_gpu) as sess: if use_static_shape_: a = constant_op.constant(effective_a_np) b = constant_op.constant(effective_b_np) @@ -115,7 +115,7 @@ def _GetMatMulGradientTest(a_np_, b_np_, use_static_shape_, **kwargs_): epsilon = np.finfo(a_np_.dtype).eps delta = epsilon**(1.0 / 3.0) tol = 20 * delta - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): a = constant_op.constant(effective_a_np) b = constant_op.constant(effective_b_np) res = math_ops.matmul(a, b, **kwargs_) diff --git a/tensorflow/python/kernel_tests/matrix_band_part_op_test.py b/tensorflow/python/kernel_tests/matrix_band_part_op_test.py index 5660a29493..93a668f125 100644 --- a/tensorflow/python/kernel_tests/matrix_band_part_op_test.py +++ b/tensorflow/python/kernel_tests/matrix_band_part_op_test.py @@ -57,7 +57,7 @@ def _GetMatrixBandPartTest(dtype_, batch_shape_, shape_): if batch_shape_ is not (): band_np = np.tile(band_np, batch_shape_ + (1, 1)) for index_dtype in [dtypes_lib.int32, dtypes_lib.int64]: - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): band = array_ops.matrix_band_part( batch_mat, constant_op.constant(lower, index_dtype), @@ -76,7 +76,7 @@ def _GetMatrixBandPartGradTest(dtype_, batch_shape_, shape_): def Test(self): shape = batch_shape_ + shape_ x = constant_op.constant(np.random.rand(*shape), dtype=dtype_) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for lower in -1, 0, 1, shape_[-2] - 1: for upper in -1, 0, 1, shape_[-1] - 1: y = array_ops.matrix_band_part(x, lower, upper) diff --git a/tensorflow/python/kernel_tests/matrix_exponential_op_test.py b/tensorflow/python/kernel_tests/matrix_exponential_op_test.py index 9630c052b8..3abdf50ece 100644 --- a/tensorflow/python/kernel_tests/matrix_exponential_op_test.py +++ b/tensorflow/python/kernel_tests/matrix_exponential_op_test.py @@ -50,7 +50,7 @@ class ExponentialOpTest(test.TestCase): def _verifyExponential(self, x, np_type): inp = x.astype(np_type) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_ans = linalg_impl.matrix_exponential(inp) if x.size == 0: np_ans = np.empty(x.shape, dtype=np_type) @@ -138,14 +138,14 @@ class ExponentialOpTest(test.TestCase): self._verifyExponentialReal(np.empty([2, 0, 0])) def testDynamic(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: inp = array_ops.placeholder(ops.dtypes.float32) expm = linalg_impl.matrix_exponential(inp) matrix = np.array([[1., 2.], [3., 4.]]) sess.run(expm, feed_dict={inp: matrix}) def testConcurrentExecutesWithoutError(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: matrix1 = random_ops.random_normal([5, 5], seed=42) matrix2 = random_ops.random_normal([5, 5], seed=42) expm1 = linalg_impl.matrix_exponential(matrix1) diff --git a/tensorflow/python/kernel_tests/matrix_inverse_op_test.py b/tensorflow/python/kernel_tests/matrix_inverse_op_test.py index 8bda04b53d..2247f1541e 100644 --- a/tensorflow/python/kernel_tests/matrix_inverse_op_test.py +++ b/tensorflow/python/kernel_tests/matrix_inverse_op_test.py @@ -37,7 +37,7 @@ class InverseOpTest(test.TestCase): def _verifyInverse(self, x, np_type): for adjoint in False, True: y = x.astype(np_type) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # Verify that x^{-1} * x == Identity matrix. inv = linalg_ops.matrix_inverse(y, adjoint=adjoint) tf_ans = math_ops.matmul(inv, y, adjoint_b=adjoint) @@ -138,7 +138,7 @@ class InverseOpTest(test.TestCase): self._verifyInverseReal(matrix) def testConcurrentExecutesWithoutError(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: all_ops = [] for adjoint_ in True, False: matrix1 = random_ops.random_normal([5, 5], seed=42) diff --git a/tensorflow/python/kernel_tests/matrix_logarithm_op_test.py b/tensorflow/python/kernel_tests/matrix_logarithm_op_test.py index 3205e211d9..2010a4b2a8 100644 --- a/tensorflow/python/kernel_tests/matrix_logarithm_op_test.py +++ b/tensorflow/python/kernel_tests/matrix_logarithm_op_test.py @@ -39,7 +39,7 @@ class LogarithmOpTest(test.TestCase): def _verifyLogarithm(self, x, np_type): inp = x.astype(np_type) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # Verify that expm(logm(A)) == A. tf_ans = linalg_impl.matrix_exponential( gen_linalg_ops.matrix_logarithm(inp)) @@ -121,7 +121,7 @@ class LogarithmOpTest(test.TestCase): self._verifyLogarithmComplex(matrix) def testConcurrentExecutesWithoutError(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: matrix1 = math_ops.cast( random_ops.random_normal([5, 5], seed=42), dtypes.complex64) matrix2 = math_ops.cast( diff --git a/tensorflow/python/kernel_tests/matrix_solve_ls_op_test.py b/tensorflow/python/kernel_tests/matrix_solve_ls_op_test.py index 225a10e117..13a7df7f95 100644 --- a/tensorflow/python/kernel_tests/matrix_solve_ls_op_test.py +++ b/tensorflow/python/kernel_tests/matrix_solve_ls_op_test.py @@ -107,7 +107,7 @@ class MatrixSolveLsOpTest(test_lib.TestCase): b = np.tile(b, batch_shape + (1, 1)) np_ans = np.tile(np_ans, batch_shape + (1, 1)) np_r_norm = np.tile(np_r_norm, batch_shape) - with self.test_session(use_gpu=fast) as sess: + with self.cached_session(use_gpu=fast) as sess: if use_placeholder: a_ph = array_ops.placeholder(dtypes.as_dtype(dtype)) b_ph = array_ops.placeholder(dtypes.as_dtype(dtype)) @@ -135,7 +135,7 @@ class MatrixSolveLsOpTest(test_lib.TestCase): def testWrongDimensions(self): # The matrix and right-hand sides should have the same number of rows. - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): matrix = constant_op.constant([[1., 0.], [0., 1.]]) rhs = constant_op.constant([[1., 0.]]) with self.assertRaises(ValueError): @@ -146,7 +146,7 @@ class MatrixSolveLsOpTest(test_lib.TestCase): empty0 = np.empty([3, 0]) empty1 = np.empty([0, 2]) for fast in [True, False]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_ans = linalg_ops.matrix_solve_ls(empty0, empty0, fast=fast).eval() self.assertEqual(tf_ans.shape, (0, 0)) tf_ans = linalg_ops.matrix_solve_ls(empty0, full, fast=fast).eval() diff --git a/tensorflow/python/kernel_tests/matrix_solve_op_test.py b/tensorflow/python/kernel_tests/matrix_solve_op_test.py index 264df2565c..9e30ae1628 100644 --- a/tensorflow/python/kernel_tests/matrix_solve_op_test.py +++ b/tensorflow/python/kernel_tests/matrix_solve_op_test.py @@ -55,7 +55,7 @@ class MatrixSolveOpTest(test.TestCase): b = np.tile(b, batch_dims + [1, 1]) np_ans = np.linalg.solve(a_np, b) for use_placeholder in False, True: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: if use_placeholder: a_ph = array_ops.placeholder(dtypes.as_dtype(np_type)) b_ph = array_ops.placeholder(dtypes.as_dtype(np_type)) @@ -93,14 +93,14 @@ class MatrixSolveOpTest(test.TestCase): def testNonSquareMatrix(self): # When the solve of a non-square matrix is attempted we should return # an error - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(ValueError): matrix = constant_op.constant([[1., 2., 3.], [3., 4., 5.]]) linalg_ops.matrix_solve(matrix, matrix) def testWrongDimensions(self): # The matrix and right-hand sides should have the same number of rows. - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): matrix = constant_op.constant([[1., 0.], [0., 1.]]) rhs = constant_op.constant([[1., 0.]]) with self.assertRaises(ValueError): @@ -108,7 +108,7 @@ class MatrixSolveOpTest(test.TestCase): def testNotInvertible(self): # The input should be invertible. - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesOpError("Input matrix is not invertible."): # All rows of the matrix below add to zero matrix = constant_op.constant([[1., 0., -1.], [-1., 1., 0.], @@ -116,7 +116,7 @@ class MatrixSolveOpTest(test.TestCase): linalg_ops.matrix_solve(matrix, matrix).eval() def testConcurrent(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: all_ops = [] for adjoint_ in False, True: lhs1 = random_ops.random_normal([3, 3], seed=42) diff --git a/tensorflow/python/kernel_tests/matrix_triangular_solve_op_test.py b/tensorflow/python/kernel_tests/matrix_triangular_solve_op_test.py index dd01ba11af..445faca3ee 100644 --- a/tensorflow/python/kernel_tests/matrix_triangular_solve_op_test.py +++ b/tensorflow/python/kernel_tests/matrix_triangular_solve_op_test.py @@ -74,7 +74,7 @@ class MatrixTriangularSolveOpTest(test.TestCase): a_np = np.tile(a_np, batch_dims + [1, 1]) b = np.tile(b, batch_dims + [1, 1]) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: if use_placeholder: a_tf = array_ops.placeholder(a.dtype) b_tf = array_ops.placeholder(b.dtype) diff --git a/tensorflow/python/kernel_tests/morphological_ops_test.py b/tensorflow/python/kernel_tests/morphological_ops_test.py index ce4d8acfbd..6d601554b8 100644 --- a/tensorflow/python/kernel_tests/morphological_ops_test.py +++ b/tensorflow/python/kernel_tests/morphological_ops_test.py @@ -44,7 +44,7 @@ class DilationTest(test.TestCase): strides = [1] + strides + [1] rates = [1] + rates + [1] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): out_tensor = nn_ops.dilation2d( constant_op.constant(image), constant_op.constant(kernel), @@ -204,7 +204,7 @@ class DilationTest(test.TestCase): strides = [1] + strides + [1] rates = [1] + rates + [1] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): image_tensor = constant_op.constant( image, shape=image_shape, name="input") kernel_tensor = constant_op.constant( @@ -319,7 +319,7 @@ class ErosionTest(test.TestCase): strides = [1] + strides + [1] rates = [1] + rates + [1] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): out_tensor = nn_ops.erosion2d( constant_op.constant(image), constant_op.constant(kernel), @@ -479,7 +479,7 @@ class ErosionTest(test.TestCase): strides = [1] + strides + [1] rates = [1] + rates + [1] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): image_tensor = constant_op.constant( image, shape=image_shape, name="input") kernel_tensor = constant_op.constant( diff --git a/tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py b/tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py index 3cbbd48c8c..15e3826542 100644 --- a/tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py +++ b/tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py @@ -114,7 +114,7 @@ class DepthwiseConv2DTest(test.TestCase): # Initializes the input and filter tensor with numbers incrementing 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)] - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: with sess.graph._kernel_label_map({"DepthwiseConv2dNative": "neon"}): t1 = constant_op.constant(x1, shape=tensor_in_sizes) t1.set_shape(tensor_in_sizes) @@ -204,7 +204,7 @@ class DepthwiseConv2DTest(test.TestCase): # 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)] - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: with sess.graph._kernel_label_map({"DepthwiseConv2dNative": "neon"}): t1 = constant_op.constant(x1, shape=tensor_in_sizes) t1.set_shape(tensor_in_sizes) diff --git a/tensorflow/python/kernel_tests/norm_op_test.py b/tensorflow/python/kernel_tests/norm_op_test.py index 3f71b326a2..e202b6e8a4 100644 --- a/tensorflow/python/kernel_tests/norm_op_test.py +++ b/tensorflow/python/kernel_tests/norm_op_test.py @@ -65,7 +65,7 @@ def _GetNormOpTest(dtype_, shape_, ord_, axis_, keep_dims_, use_static_shape_): def _CompareNorm(self, matrix): np_norm = np.linalg.norm(matrix, ord=ord_, axis=axis_, keepdims=keep_dims_) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: if use_static_shape_: tf_matrix = constant_op.constant(matrix) tf_norm = linalg_ops.norm( diff --git a/tensorflow/python/kernel_tests/nth_element_op_test.py b/tensorflow/python/kernel_tests/nth_element_op_test.py index 1b8f02140f..338b6cec01 100644 --- a/tensorflow/python/kernel_tests/nth_element_op_test.py +++ b/tensorflow/python/kernel_tests/nth_element_op_test.py @@ -32,7 +32,7 @@ class NthElementTest(test.TestCase): def _validateNthElement(self, inputs, dtype, n, reverse, expected_values): np_expected_values = np.array(expected_values) - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: inputs_op = ops.convert_to_tensor(inputs, dtype=dtype) values_op = nn_ops.nth_element(inputs_op, n, reverse=reverse) values = sess.run(values_op) @@ -117,7 +117,7 @@ class NthElementTest(test.TestCase): nn_ops.nth_element(5, 0) def testInvalidInputAtEval(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): v = array_ops.placeholder(dtype=dtypes.float32) with self.assertRaisesOpError("Input must be >= 1-D"): nn_ops.nth_element(v, 0).eval(feed_dict={v: 5.0}) @@ -132,7 +132,7 @@ class NthElementTest(test.TestCase): def testInvalidNAtEval(self): inputs = [[0.1, 0.2], [0.3, 0.4]] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): n = array_ops.placeholder(dtypes.int32) values = nn_ops.nth_element(inputs, n) with self.assertRaisesOpError("Need n >= 0, got -7"): @@ -146,14 +146,14 @@ class NthElementTest(test.TestCase): def testNTooLargeAtEval(self): inputs = [[0.1, 0.2], [0.3, 0.4]] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): n = array_ops.placeholder(dtypes.int32) values = nn_ops.nth_element(inputs, n) with self.assertRaisesOpError(r"Input must have at least n\+1 columns"): values.eval(feed_dict={n: 2}) def testGradients(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: inputs = array_ops.placeholder(dtypes.float32, shape=[3, 5]) values = nn_ops.nth_element(inputs, 3) grad = sess.run( diff --git a/tensorflow/python/kernel_tests/numerics_test.py b/tensorflow/python/kernel_tests/numerics_test.py index 6cc70f7c89..5db591ed30 100644 --- a/tensorflow/python/kernel_tests/numerics_test.py +++ b/tensorflow/python/kernel_tests/numerics_test.py @@ -35,7 +35,7 @@ class VerifyTensorAllFiniteTest(test.TestCase): def testVerifyTensorAllFiniteSucceeds(self): x_shape = [5, 4] x = np.random.random_sample(x_shape).astype(np.float32) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): t = constant_op.constant(x, shape=x_shape, dtype=dtypes.float32) t_verified = numerics.verify_tensor_all_finite(t, "Input is not a number.") @@ -48,7 +48,7 @@ class VerifyTensorAllFiniteTest(test.TestCase): # Test NaN. x[0] = np.nan - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesOpError(my_msg): t = constant_op.constant(x, shape=x_shape, dtype=dtypes.float32) t_verified = numerics.verify_tensor_all_finite(t, my_msg) @@ -56,7 +56,7 @@ class VerifyTensorAllFiniteTest(test.TestCase): # Test Inf. x[0] = np.inf - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesOpError(my_msg): t = constant_op.constant(x, shape=x_shape, dtype=dtypes.float32) t_verified = numerics.verify_tensor_all_finite(t, my_msg) diff --git a/tensorflow/python/kernel_tests/one_hot_op_test.py b/tensorflow/python/kernel_tests/one_hot_op_test.py index b449a195a7..377d545c9c 100644 --- a/tensorflow/python/kernel_tests/one_hot_op_test.py +++ b/tensorflow/python/kernel_tests/one_hot_op_test.py @@ -34,7 +34,7 @@ class OneHotTest(test.TestCase): expected_err_re=None, raises=None, **inputs): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): if raises is not None: with self.assertRaises(raises): array_ops.one_hot(**inputs) diff --git a/tensorflow/python/kernel_tests/pad_op_test.py b/tensorflow/python/kernel_tests/pad_op_test.py index e415d7879e..fc302c4141 100644 --- a/tensorflow/python/kernel_tests/pad_op_test.py +++ b/tensorflow/python/kernel_tests/pad_op_test.py @@ -85,7 +85,7 @@ class PadOpTest(test.TestCase): def _testPad(self, np_inputs, paddings, mode, constant_values): np_val = self._npPad(np_inputs, paddings, mode=mode, constant_values=constant_values) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_val = array_ops.pad(np_inputs, paddings, mode=mode, constant_values=constant_values) out = tf_val.eval() @@ -93,7 +93,7 @@ class PadOpTest(test.TestCase): self.assertShapeEqual(np_val, tf_val) def _testGradient(self, x, a, mode, constant_values): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(x) xs = list(x.shape) ina = ops.convert_to_tensor(a) @@ -117,7 +117,7 @@ class PadOpTest(test.TestCase): constant_values=constant_values) def testInputDims(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(ValueError): array_ops.pad(array_ops.reshape( [1, 2], shape=[1, 2, 1, 1, 1, 1]), @@ -125,7 +125,7 @@ class PadOpTest(test.TestCase): [1, 2], shape=[1, 2])) def testPaddingsDim(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(ValueError): array_ops.pad(array_ops.reshape( [1, 2], shape=[1, 2]), @@ -133,7 +133,7 @@ class PadOpTest(test.TestCase): [1, 2], shape=[2])) def testPaddingsDim2(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(ValueError): array_ops.pad(array_ops.reshape( [1, 2], shape=[1, 2]), @@ -141,7 +141,7 @@ class PadOpTest(test.TestCase): [1, 2], shape=[2, 1])) def testPaddingsDim3(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(ValueError): array_ops.pad(array_ops.reshape( [1, 2], shape=[1, 2]), @@ -149,7 +149,7 @@ class PadOpTest(test.TestCase): [1, 2], shape=[1, 2])) def testPaddingsDim4(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(ValueError): array_ops.pad(array_ops.reshape( [1, 2], shape=[1, 2]), @@ -157,7 +157,7 @@ class PadOpTest(test.TestCase): [1, 2, 3, 4, 5, 6], shape=[3, 2])) def testPaddingsNonNegative(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesRegexp(ValueError, "must be non-negative"): array_ops.pad(constant_op.constant( [1], shape=[1]), @@ -165,7 +165,7 @@ class PadOpTest(test.TestCase): [-1, 0], shape=[1, 2])) def testPaddingsNonNegative2(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesRegexp(ValueError, "must be non-negative"): array_ops.pad(constant_op.constant( [1], shape=[1]), @@ -173,7 +173,7 @@ class PadOpTest(test.TestCase): [-1, 0], shape=[1, 2])) def testPaddingsMaximum(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(Exception): array_ops.pad(constant_op.constant( [1], shape=[2]), @@ -203,7 +203,7 @@ class PadOpTest(test.TestCase): paddings, mode=mode, constant_values=0) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_val = array_ops.pad(inputs, constant_op.constant(paddings, padding_dtype), mode=mode, @@ -249,7 +249,7 @@ class PadOpTest(test.TestCase): constant_values="PAD") symmetric = array_ops.pad(x, [[1, 0], [0, 1]], mode="SYMMETRIC", constant_values="PAD") - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual([[b"PAD", b"PAD", b"PAD"], [b"Hello", b"World", b"PAD"], [b"Goodnight", b"Moon", b"PAD"]], constant.eval()) @@ -325,7 +325,7 @@ class PadOpTest(test.TestCase): def testScalars(self): paddings = np.zeros((0, 2), dtype=np.int32) inp = np.asarray(7) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tf_val = array_ops.pad(inp, paddings) out = tf_val.eval() self.assertAllEqual(inp, out) @@ -335,7 +335,7 @@ class PadOpTest(test.TestCase): for dtype in [dtypes.int32, dtypes.int64]: paddings = np.zeros((0, 2)) inp = np.asarray(7) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_val = array_ops.pad(inp, constant_op.constant(paddings, dtype=dtype)) out = tf_val.eval() self.assertAllEqual(inp, out) @@ -360,7 +360,7 @@ class PadOpTest(test.TestCase): padded, [paddings_value[i][0] + inp.shape.dims[i].value for i in range(4)], [-1, -1, -1, -1]) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): self.assertAllEqual(inp.eval(), middle.eval()) self.assertAllEqual( np.zeros([row[0] for row in paddings_value]), left.eval()) diff --git a/tensorflow/python/kernel_tests/parameterized_truncated_normal_op_test.py b/tensorflow/python/kernel_tests/parameterized_truncated_normal_op_test.py index e14894cf56..53b713c03e 100644 --- a/tensorflow/python/kernel_tests/parameterized_truncated_normal_op_test.py +++ b/tensorflow/python/kernel_tests/parameterized_truncated_normal_op_test.py @@ -115,7 +115,7 @@ class ParameterizedTruncatedNormalTest(test.TestCase): # Give up early if we are unable to import it. import scipy.stats # pylint: disable=g-import-not-at-top,unused-variable random_seed.set_random_seed(seed) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): samples = random_ops.parameterized_truncated_normal(shape, mean, stddev, minval, maxval).eval() @@ -139,7 +139,7 @@ class ParameterizedTruncatedNormalTest(test.TestCase): try: import scipy.stats # pylint: disable=g-import-not-at-top random_seed.set_random_seed(seed) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): samples = random_ops.parameterized_truncated_normal(shape, mean, stddev, minval, maxval).eval() @@ -186,7 +186,7 @@ class ParameterizedTruncatedNormalTest(test.TestCase): sample_op = random_ops.parameterized_truncated_normal( shape=(int(1e5),), means=0.8, stddevs=0.05, minvals=-1., maxvals=1.) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: samples = sess.run(sample_op) # 0. is more than 16 standard deviations from the mean, and # should have a likelihood < 1e-57. diff --git a/tensorflow/python/kernel_tests/partitioned_variables_test.py b/tensorflow/python/kernel_tests/partitioned_variables_test.py index b34d30f5c0..d1f0c6c2a0 100644 --- a/tensorflow/python/kernel_tests/partitioned_variables_test.py +++ b/tensorflow/python/kernel_tests/partitioned_variables_test.py @@ -600,7 +600,7 @@ class PartitionedVariablesTestCase(test.TestCase): def testMetaGraphSaveLoad(self): save_prefix = os.path.join(self.get_temp_dir(), "ckpt") save_graph = ops.Graph() - with save_graph.as_default(), self.test_session( + with save_graph.as_default(), self.session( graph=save_graph) as session: partitioner = partitioned_variables.fixed_size_partitioner(5, axis=0) with variable_scope.variable_scope("root", partitioner=partitioner): @@ -620,7 +620,7 @@ class PartitionedVariablesTestCase(test.TestCase): save_graph.get_tensor_by_name(v0.name + ":0")) restore_graph = ops.Graph() - with restore_graph.as_default(), self.test_session( + with restore_graph.as_default(), self.session( graph=restore_graph) as session: saver = saver_lib.import_meta_graph(save_path + ".meta") saver.restore(sess=session, save_path=save_path) diff --git a/tensorflow/python/kernel_tests/pool_test.py b/tensorflow/python/kernel_tests/pool_test.py index 6ede654aad..372861297f 100644 --- a/tensorflow/python/kernel_tests/pool_test.py +++ b/tensorflow/python/kernel_tests/pool_test.py @@ -154,7 +154,7 @@ class PoolingTest(test.TestCase): self.assertAllClose(y1, y2.eval(), rtol=1e-2, atol=1e-2) def testPoolSimple(self): - with self.test_session(use_gpu=test.is_gpu_available()): + with self.session(use_gpu=test.is_gpu_available()): for padding in ["SAME", "VALID"]: for pooling_type in ["MAX", "AVG"]: self._test( @@ -166,7 +166,7 @@ class PoolingTest(test.TestCase): strides=[1, 2]) def testPool1D(self): - with self.test_session(use_gpu=test.is_gpu_available()): + with self.session(use_gpu=test.is_gpu_available()): for padding in ["SAME", "VALID"]: for pooling_type in ["MAX", "AVG"]: for input_shape in [[2, 9, 2], [2, 10, 2]]: @@ -192,7 +192,7 @@ class PoolingTest(test.TestCase): strides=strides) def testPool2D(self): - with self.test_session(use_gpu=test.is_gpu_available()): + with self.session(use_gpu=test.is_gpu_available()): for padding in ["SAME", "VALID"]: for pooling_type in ["MAX", "AVG"]: for input_shape in [[2, 9, 10, 2], [2, 10, 9, 2]]: @@ -218,7 +218,7 @@ class PoolingTest(test.TestCase): strides=strides) def testPool3D(self): - with self.test_session(use_gpu=test.is_gpu_available()): + with self.session(use_gpu=test.is_gpu_available()): for padding in ["SAME", "VALID"]: for pooling_type in ["MAX", "AVG"]: for input_shape in [[2, 9, 10, 11, 2], [2, 10, 9, 11, 2]]: @@ -247,7 +247,7 @@ class PoolingTest(test.TestCase): def testPoolNC(self): if test.is_gpu_available(cuda_only=True): # "NC*" format is currently only supported on CUDA. - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for padding in ["SAME", "VALID"]: self._test( input_shape=[2, 2, 9], @@ -302,7 +302,7 @@ class PoolingTest(test.TestCase): self.assertLess(err, err_tolerance) def testGradient1D(self): - with self.test_session(use_gpu=test.is_gpu_available()): + with self.session(use_gpu=test.is_gpu_available()): for padding in ["SAME", "VALID"]: for pooling_type in ["AVG", "MAX"]: for input_shape in [[2, 5, 2], [1, 4, 1]]: @@ -328,7 +328,7 @@ class PoolingTest(test.TestCase): strides=strides) def testGradient2D(self): - with self.test_session(use_gpu=test.is_gpu_available()): + with self.session(use_gpu=test.is_gpu_available()): for padding in ["SAME", "VALID"]: for pooling_type in ["AVG", "MAX"]: for input_shape in [[2, 4, 5, 2], [1, 5, 4, 1]]: @@ -354,7 +354,7 @@ class PoolingTest(test.TestCase): strides=strides) def testGradient3D(self): - with self.test_session(use_gpu=test.is_gpu_available()): + with self.session(use_gpu=test.is_gpu_available()): for padding in ["SAME", "VALID"]: for pooling_type in ["AVG", "MAX"]: for input_shape in [[1, 3, 5, 4, 1], [1, 5, 4, 3, 1]]: diff --git a/tensorflow/python/kernel_tests/pooling_ops_3d_test.py b/tensorflow/python/kernel_tests/pooling_ops_3d_test.py index b01fc12953..e393c7a022 100644 --- a/tensorflow/python/kernel_tests/pooling_ops_3d_test.py +++ b/tensorflow/python/kernel_tests/pooling_ops_3d_test.py @@ -65,7 +65,7 @@ class PoolingTest(test.TestCase): # Initializes the input tensor with array containing incrementing # numbers from 1. x = [f * 1.0 for f in range(1, total_size + 1)] - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: t = constant_op.constant(x, shape=input_sizes) window = [1] + list(window) + [1] strides = [1] + list(strides) + [1] @@ -233,7 +233,7 @@ class PoolingTest(test.TestCase): # Initializes the input tensor with array containing incrementing # numbers from 1. x = np.arange(1, total_size + 1, dtype=np.float32) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): input_tensor = constant_op.constant(x, shape=input_sizes, name="input") err_g_margin = 1e-3 err_gg_margin = 1.5e-2 diff --git a/tensorflow/python/kernel_tests/pooling_ops_test.py b/tensorflow/python/kernel_tests/pooling_ops_test.py index e95c729715..53003a7f28 100644 --- a/tensorflow/python/kernel_tests/pooling_ops_test.py +++ b/tensorflow/python/kernel_tests/pooling_ops_test.py @@ -129,7 +129,7 @@ class PoolingTest(test.TestCase): # Initializes the input tensor with array containing incrementing # numbers from 1, wrapping round to -127 after 127 to support int8. x = [((f + 128) % 255) - 127 for f in range(total_size)] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): t = constant_op.constant(x, shape=input_sizes, dtype=data_type) if data_format in ("NCHW", "NCHW_VECT_C"): if data_format == "NCHW_VECT_C": @@ -718,7 +718,7 @@ class PoolingTest(test.TestCase): strides, error_msg, use_gpu=False): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): t = constant_op.constant(1.0, shape=in_size) with self.assertRaisesRegexp(errors_impl.UnimplementedError, error_msg): t = nn_ops.max_pool( @@ -734,7 +734,7 @@ class PoolingTest(test.TestCase): self._testDepthwiseMaxPoolInvalidConfig([1, 2, 2, 4], [1, 1, 1, 3], [1, 1, 1, 3], "evenly divide") if test.is_gpu_available(): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): t = variables.Variable(np.ones([1, 2, 2, 4])) variables.global_variables_initializer().run() with self.assertRaisesOpError("for CPU devices"): @@ -747,11 +747,11 @@ class PoolingTest(test.TestCase): def _CompareMaxPoolingFwd(self, input_shape, ksize, strides, padding): for dtype in np.float64, np.float32, np.float16: tensor_input = np.random.rand(*input_shape).astype(dtype) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): t = constant_op.constant(tensor_input, shape=input_shape) out_op, _ = nn_ops.max_pool_with_argmax(t, ksize, strides, padding) gpu_val = out_op.eval() - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): t = constant_op.constant(tensor_input, shape=input_shape) out_op = nn_ops.max_pool(t, ksize, strides, padding) cpu_val = out_op.eval() @@ -764,7 +764,7 @@ class PoolingTest(test.TestCase): # in the input. tensor_input = np.random.random_integers(0, 3, input_shape).astype(dtype) tensor_output = np.random.rand(*output_shape).astype(dtype) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): t = constant_op.constant(tensor_input, shape=input_shape) _, argmax_op = nn_ops.max_pool_with_argmax(t, ksize, strides, padding) argmax = argmax_op.eval() @@ -773,7 +773,7 @@ class PoolingTest(test.TestCase): strides, padding) gpu_val = out_op.eval() self.assertShapeEqual(gpu_val, out_op) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): t = constant_op.constant(tensor_input, shape=input_shape) out_op = nn_ops.max_pool(t, ksize, strides, padding) orig_out = out_op.eval() @@ -793,7 +793,7 @@ class PoolingTest(test.TestCase): # Generate numbers in a narrow range, so that there are many duplicates # in the input. tensor_input = np.random.random_integers(0, 3, input_shape).astype(dtype) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): t = constant_op.constant(tensor_input, shape=input_shape) _, argmax_op = nn_ops.max_pool_with_argmax(t, ksize, strides, padding) argmax = argmax_op.eval() @@ -802,7 +802,7 @@ class PoolingTest(test.TestCase): t, grad_in, argmax, ksize, strides, padding) gpu_val = out_op.eval() self.assertShapeEqual(gpu_val, out_op) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): t = constant_op.constant(tensor_input, shape=input_shape) out_op = nn_ops.max_pool(t, ksize, strides, padding) orig_out = out_op.eval() @@ -818,7 +818,7 @@ class PoolingTest(test.TestCase): def testMaxPoolingWithArgmax(self): tensor_input = [1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: t = constant_op.constant(tensor_input, shape=[1, 3, 3, 1]) out_op, argmax_op = nn_ops.max_pool_with_argmax( t, @@ -836,7 +836,7 @@ class PoolingTest(test.TestCase): orig_input = [1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0] tensor_input = [11.0, 12.0, 13.0, 14.0] tensor_argmax = list(np.array([0, 1, 3, 5], dtype=np.int64)) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): orig_in = constant_op.constant(orig_input, shape=[1, 3, 3, 1]) t = constant_op.constant(tensor_input, shape=[1, 2, 2, 1]) argmax = constant_op.constant( @@ -859,7 +859,7 @@ class PoolingTest(test.TestCase): orig_input = [1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0] tensor_input = [11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0] tensor_argmax = list(np.array([0, 1, 3, 5], dtype=np.int64)) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): orig_in = constant_op.constant(orig_input, shape=[1, 3, 3, 1]) t = constant_op.constant(tensor_input, shape=[1, 3, 3, 1]) argmax = constant_op.constant( @@ -910,7 +910,7 @@ class PoolingTest(test.TestCase): # Initializes the input tensor with array containing incrementing # numbers from 1. x = [f * 1.0 for f in range(1, total_size + 1)] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): input_tensor = constant_op.constant(x, shape=input_sizes, name="input") if pool_func == nn_ops.avg_pool: func_name = "avg_pool" @@ -986,7 +986,7 @@ class PoolingTest(test.TestCase): # Initializes the input tensor with array containing incrementing # numbers from 1. x = [f * 1.0 for f in range(1, total_size + 1)] - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): input_tensor = constant_op.constant(x, shape=input_sizes, name="input") if pool_func == nn_ops.avg_pool: func_name = "avg_pool" @@ -1208,7 +1208,7 @@ class PoolingTest(test.TestCase): window_rows, window_cols, row_stride, col_stride, padding, use_gpu, v2): pool_func = gen_nn_ops.max_pool_v2 if v2 else nn_ops.max_pool - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): input_tensor = variables.Variable( np.array(input_data, dtype=np.float32).reshape(input_sizes)) variables.global_variables_initializer().run() @@ -1807,7 +1807,7 @@ class PoolingTest(test.TestCase): padding="SAME") def testOpEdgeCases(self): - with self.test_session(use_gpu=test.is_gpu_available()) as sess: + with self.session(use_gpu=test.is_gpu_available()) as sess: pool_funcs = [nn_ops.max_pool, nn_ops.avg_pool] if test.is_gpu_available(): pool_funcs.append(nn_ops.max_pool_with_argmax) diff --git a/tensorflow/python/kernel_tests/py_func_test.py b/tensorflow/python/kernel_tests/py_func_test.py index 5f5e24bd63..837f1ec054 100644 --- a/tensorflow/python/kernel_tests/py_func_test.py +++ b/tensorflow/python/kernel_tests/py_func_test.py @@ -644,7 +644,7 @@ class PyFuncTest(test.TestCase): y = script_ops.eager_py_func(func=f, inp=[x], Tout=dtypes.float32) z = script_ops.eager_py_func(func=g, inp=[y], Tout=dtypes.float32) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: output = sess.run(z, feed_dict={x: 3.0}) self.assertEqual(output, 18.0) diff --git a/tensorflow/python/kernel_tests/qr_op_test.py b/tensorflow/python/kernel_tests/qr_op_test.py index 8848c15e76..a60237fb25 100644 --- a/tensorflow/python/kernel_tests/qr_op_test.py +++ b/tensorflow/python/kernel_tests/qr_op_test.py @@ -50,7 +50,7 @@ class QrOpTest(test.TestCase): linalg_ops.qr(vector) def testConcurrentExecutesWithoutError(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: all_ops = [] for full_matrices_ in True, False: for rows_ in 4, 5: @@ -121,7 +121,7 @@ def _GetQrOpTest(dtype_, shape_, full_matrices_, use_static_shape_): low=-1.0, high=1.0, size=np.prod(shape_)).reshape(shape_).astype(dtype_) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: if use_static_shape_: x_tf = constant_op.constant(x_np) else: @@ -173,7 +173,7 @@ def _GetQrGradOpTest(dtype_, shape_, full_matrices_): tol = 3e-2 else: tol = 1e-6 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tf_a = constant_op.constant(a) tf_b = linalg_ops.qr(tf_a, full_matrices=full_matrices_) for b in tf_b: diff --git a/tensorflow/python/kernel_tests/reader_ops_test.py b/tensorflow/python/kernel_tests/reader_ops_test.py index 8c84b2a49f..ac9be56d63 100644 --- a/tensorflow/python/kernel_tests/reader_ops_test.py +++ b/tensorflow/python/kernel_tests/reader_ops_test.py @@ -707,7 +707,7 @@ class AsyncReaderTest(test.TestCase): """Tests that reading does not block main execution threads.""" config = config_pb2.ConfigProto( inter_op_parallelism_threads=1, intra_op_parallelism_threads=1) - with self.test_session(config=config) as sess: + with self.session(config=config) as sess: thread_data_t = collections.namedtuple("thread_data_t", ["thread", "queue", "output"]) thread_data = [] diff --git a/tensorflow/python/kernel_tests/reduction_ops_test.py b/tensorflow/python/kernel_tests/reduction_ops_test.py index 248036a82a..7cca170ef3 100644 --- a/tensorflow/python/kernel_tests/reduction_ops_test.py +++ b/tensorflow/python/kernel_tests/reduction_ops_test.py @@ -131,7 +131,7 @@ class BaseReductionTest(test.TestCase): def _compare(self, x, reduction_axes, keepdims, feed_dict=None): np_ans = self._np_reduce(x, reduction_axes, keepdims) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: tf_ans = self._tf_reduce(x, reduction_axes, keepdims) out = sess.run(tf_ans, feed_dict) self.assertAllClose(np_ans, out) @@ -153,7 +153,7 @@ class BaseReductionTest(test.TestCase): if reduction_axes is not None and np.shape(reduction_axes) == (1,): # Test scalar reduction_axes argument self._compareGradient(x, reduction_axes[0], rtol=rtol, atol=atol) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): t = ops.convert_to_tensor(x) su = self._tf_reduce(t, reduction_axes, False) jacob_t, jacob_n = gradient_checker.compute_gradient( @@ -183,7 +183,7 @@ class SumReductionTest(BaseReductionTest): def testAxesType(self): for dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: v = math_ops.reduce_sum([0, 0], constant_op.constant(0, dtype=dtype)) tf_v = sess.run(v) self.assertAllEqual(tf_v, 0) @@ -356,14 +356,14 @@ class SumReductionTest(BaseReductionTest): self._compareAll(x, [1]) def testEmptyGradients(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = array_ops.zeros([0, 3]) y = math_ops.reduce_sum(x, [1]) error = gradient_checker.compute_gradient_error(x, [0, 3], y, [0]) self.assertEqual(error, 0) def testDegenerate(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in (dtypes.float16, dtypes.float32, dtypes.float64, dtypes.complex64, dtypes.complex128): # A large number is needed to get Eigen to die @@ -398,7 +398,7 @@ class MeanReductionTest(BaseReductionTest): def testAxesType(self): for dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: v = math_ops.reduce_mean([0, 0], constant_op.constant(0, dtype=dtype)) tf_v = sess.run(v) self.assertAllEqual(tf_v, 0) @@ -442,14 +442,14 @@ class MeanReductionTest(BaseReductionTest): self._compareGradientAxes(x, rtol=1e-3, atol=1e-3) def testEmptyGradients(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = array_ops.zeros([0, 3]) y = math_ops.reduce_mean(x, [1]) error = gradient_checker.compute_gradient_error(x, [0, 3], y, [0]) self.assertEqual(error, 0) def testDegenerate(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in (dtypes.float16, dtypes.float32, dtypes.float64): # A large number is needed to get Eigen to die x = array_ops.zeros((0, 9938), dtype=dtype) @@ -471,7 +471,7 @@ class ProdReductionTest(BaseReductionTest): def testAxesType(self): for dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: v = math_ops.reduce_prod([0, 0], constant_op.constant(0, dtype=dtype)) tf_v = sess.run(v) self.assertAllEqual(tf_v, 0) @@ -534,14 +534,14 @@ class ProdReductionTest(BaseReductionTest): self._compareGradientAxes(x4, rtol=1e-3, atol=1e-3) def testEmptyGradients(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): x = array_ops.zeros([0, 3]) y = math_ops.reduce_prod(x, [1]) error = gradient_checker.compute_gradient_error(x, [0, 3], y, [0]) self.assertEqual(error, 0) def testDegenerate(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in (dtypes.float16, dtypes.float32, dtypes.float64): # A large number is needed to get Eigen to die x = array_ops.zeros((0, 9938), dtype=dtype) @@ -558,7 +558,7 @@ class MinReductionTest(test.TestCase): else: for ra in reduction_axes[::-1]: np_ans = np.amin(np_ans, axis=ra, keepdims=keepdims) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): if reduction_axes is not None: reduction_axes = np.array(reduction_axes).astype(np.int32) tf_ans = math_ops.reduce_min(x, reduction_axes, keepdims) @@ -574,7 +574,7 @@ class MinReductionTest(test.TestCase): def testAxesType(self): for dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: v = math_ops.reduce_min([0, 0], constant_op.constant(0, dtype=dtype)) tf_v = sess.run(v) self.assertAllEqual(tf_v, 0) @@ -671,7 +671,7 @@ class MaxReductionTest(test.TestCase): else: for ra in reduction_axes[::-1]: np_ans = np.amax(np_ans, axis=ra, keepdims=keepdims) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): if reduction_axes is not None: reduction_axes = np.array(reduction_axes).astype(np.int32) tf_ans = math_ops.reduce_max(x, reduction_axes, keepdims) @@ -687,7 +687,7 @@ class MaxReductionTest(test.TestCase): def testAxesType(self): for dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: v = math_ops.reduce_max([0, 0], constant_op.constant(0, dtype=dtype)) tf_v = sess.run(v) self.assertAllEqual(tf_v, 0) @@ -798,7 +798,7 @@ class AllReductionTest(test.TestCase): else: for ra in reduction_axes[::-1]: np_ans = np.all(np_ans, axis=ra, keepdims=keepdims) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): if reduction_axes is not None: reduction_axes = np.array(reduction_axes).astype(np.int32) tf_ans = math_ops.reduce_all(x, reduction_axes, keepdims) @@ -814,7 +814,7 @@ class AllReductionTest(test.TestCase): def testAxesType(self): for dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: v = math_ops.reduce_all([True, True], constant_op.constant(0, dtype=dtype)) tf_v = sess.run(v) @@ -847,7 +847,7 @@ class AnyReductionTest(test.TestCase): else: for ra in reduction_axes[::-1]: np_ans = np.any(np_ans, axis=ra, keepdims=keepdims) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): if reduction_axes is not None: reduction_axes = np.array(reduction_axes).astype(np.int32) tf_ans = math_ops.reduce_any(x, reduction_axes, keepdims) @@ -863,7 +863,7 @@ class AnyReductionTest(test.TestCase): def testAxesType(self): for dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: v = math_ops.reduce_any([True, True], constant_op.constant(0, dtype=dtype)) tf_v = sess.run(v) @@ -898,7 +898,7 @@ class CountNonzeroReductionTest(test.TestCase): reduction_axes = np.array(reduction_axes).astype(np.int32) for ra in reduction_axes.ravel()[::-1]: np_ans = np.sum(np_ans, axis=ra, keepdims=keepdims) - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: tf_ans = math_ops.count_nonzero(x, reduction_axes, keepdims) out = sess.run(tf_ans, feed_dict) self.assertAllClose(np_ans, out) @@ -951,7 +951,7 @@ class CountNonzeroReductionTest(test.TestCase): def testDegenerate(self): for use_gpu in False, True: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): for dtype in (dtypes.bool,): # A large number is needed to get Eigen to die x = array_ops.zeros((0, 9938), dtype=dtype) diff --git a/tensorflow/python/kernel_tests/relu_op_test.py b/tensorflow/python/kernel_tests/relu_op_test.py index 672d6556f5..b0f2796ede 100644 --- a/tensorflow/python/kernel_tests/relu_op_test.py +++ b/tensorflow/python/kernel_tests/relu_op_test.py @@ -57,7 +57,7 @@ class ReluTest(test.TestCase): def _testRelu(self, np_features, use_gpu=False): np_relu = self._npRelu(np_features) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): relu = nn_ops.relu(np_features) tf_relu = relu.eval() self.assertAllClose(np_relu, tf_relu) @@ -77,7 +77,7 @@ class ReluTest(test.TestCase): if not test.is_gpu_available(cuda_only=True): return np_relu = self._npRelu(np_inputs) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): relu = nn_ops.relu(constant_op.constant(np_inputs, dtypes.qint8)) if np_inputs.size % 4 == 0: tf_relu = relu.eval() @@ -124,7 +124,7 @@ class ReluTest(test.TestCase): # Instead of relying on compute_gradient_error, we compare the fp16 analytical # gradient against their fp32 counterpart. def testGradientFloat16(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: # Randomly construct a 1D shape from [1, 40) shape = random_ops.random_uniform( [1], minval=1, maxval=40, dtype=dtypes.int32) @@ -230,7 +230,7 @@ class Relu6Test(test.TestCase): def _testRelu6(self, np_features, use_gpu=False): np_relu6 = self._npRelu6(np_features) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): relu6 = nn_ops.relu6(np_features) tf_relu6 = relu6.eval() self.assertAllClose(np_relu6, tf_relu6) @@ -417,7 +417,7 @@ class EluTest(test.TestCase): def _testElu(self, np_features, use_gpu=False): np_elu = self._npElu(np_features) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): elu = nn_ops.elu(np_features) tf_elu = elu.eval() self.assertAllClose(np_elu, tf_elu) @@ -519,7 +519,7 @@ class SeluTest(test.TestCase): def _testSelu(self, np_features, use_gpu=False): np_selu = self._npSelu(np_features) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): selu = nn_ops.selu(np_features) tf_selu = selu.eval() self.assertAllClose(np_selu, tf_selu) @@ -605,7 +605,7 @@ class CreluTest(test.TestCase): np_crelu = np.concatenate((np_relu, np_neg_relu), len(np_features.shape) - 1) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): crelu = nn_ops.crelu(np_features) tf_relu = crelu.eval() diff --git a/tensorflow/python/kernel_tests/reshape_op_test.py b/tensorflow/python/kernel_tests/reshape_op_test.py index ca3ff1d1df..14cdae1837 100644 --- a/tensorflow/python/kernel_tests/reshape_op_test.py +++ b/tensorflow/python/kernel_tests/reshape_op_test.py @@ -30,7 +30,7 @@ from tensorflow.python.platform import test class ReshapeTest(test.TestCase): def _testReshape(self, x, y, use_gpu=False): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): np_ans = x.reshape(y) tf_ans = array_ops.reshape(x, y) out = tf_ans.eval() diff --git a/tensorflow/python/kernel_tests/reverse_sequence_op_test.py b/tensorflow/python/kernel_tests/reverse_sequence_op_test.py index 8fc71e0c57..56609bd0a5 100644 --- a/tensorflow/python/kernel_tests/reverse_sequence_op_test.py +++ b/tensorflow/python/kernel_tests/reverse_sequence_op_test.py @@ -38,7 +38,7 @@ class ReverseSequenceTest(test.TestCase): truth, use_gpu=False, expected_err_re=None): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): ans = array_ops.reverse_sequence( x, batch_axis=batch_axis, seq_axis=seq_axis, seq_lengths=seq_lengths) if expected_err_re is None: diff --git a/tensorflow/python/kernel_tests/rnn_test.py b/tensorflow/python/kernel_tests/rnn_test.py index 907e1277a9..993ea4b6b7 100644 --- a/tensorflow/python/kernel_tests/rnn_test.py +++ b/tensorflow/python/kernel_tests/rnn_test.py @@ -554,7 +554,7 @@ class RNNTest(test.TestCase): kernel, recurrent_kernel, bias = keras_weights tf_weights = [np.concatenate((kernel, recurrent_kernel)), bias] - with self.test_session(graph=ops_lib.Graph()) as sess: + with self.session(graph=ops_lib.Graph()) as sess: inputs = array_ops.placeholder( dtypes.float32, shape=(None, timestep, input_shape)) cell = keras.layers.SimpleRNNCell(output_shape) @@ -562,7 +562,7 @@ class RNNTest(test.TestCase): cell, inputs, dtype=dtypes.float32) cell.set_weights(keras_weights) [k_out, k_state] = sess.run([k_out, k_state], {inputs: x_train}) - with self.test_session(graph=ops_lib.Graph()) as sess: + with self.session(graph=ops_lib.Graph()) as sess: inputs = array_ops.placeholder( dtypes.float32, shape=(None, timestep, input_shape)) cell = rnn_cell_impl.BasicRNNCell(output_shape) diff --git a/tensorflow/python/kernel_tests/scan_ops_test.py b/tensorflow/python/kernel_tests/scan_ops_test.py index 08b4a2aaae..b369222565 100644 --- a/tensorflow/python/kernel_tests/scan_ops_test.py +++ b/tensorflow/python/kernel_tests/scan_ops_test.py @@ -78,7 +78,7 @@ class CumsumTest(test.TestCase): def _compare(self, x, axis, exclusive, reverse): np_out = handle_options(np.cumsum, x, axis, exclusive, reverse) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_out = math_ops.cumsum(x, axis, exclusive, reverse).eval() self.assertAllClose(np_out, tf_out) @@ -98,7 +98,7 @@ class CumsumTest(test.TestCase): for dtype in self.valid_dtypes: x = np.arange(1, 6).reshape([5]).astype(dtype) for axis_dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): axis = constant_op.constant(0, axis_dtype) tf_out = math_ops.cumsum(x, axis).eval() @@ -129,7 +129,7 @@ class CumsumTest(test.TestCase): def testInvalidAxis(self): x = np.arange(0, 10).reshape([2, 5]).astype(np.float32) input_tensor = ops.convert_to_tensor(x) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesWithPredicateMatch( errors_impl.InvalidArgumentError, lambda e: "Expected scan axis in the range [-2, 2)" in str(e)): @@ -145,7 +145,7 @@ class CumsumTest(test.TestCase): def _compareGradient(self, shape, axis, exclusive, reverse): x = np.arange(0, 50).reshape(shape).astype(np.float64) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): t = ops.convert_to_tensor(x) result = math_ops.cumsum(t, axis, exclusive, reverse) jacob_t, jacob_n = gradient_checker.compute_gradient( @@ -184,7 +184,7 @@ class CumprodTest(test.TestCase): def _compare(self, x, axis, exclusive, reverse): np_out = handle_options(np.cumprod, x, axis, exclusive, reverse) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_out = math_ops.cumprod(x, axis, exclusive, reverse).eval() self.assertAllClose(np_out, tf_out) @@ -204,7 +204,7 @@ class CumprodTest(test.TestCase): for dtype in self.valid_dtypes: x = np.arange(1, 6).reshape([5]).astype(dtype) for axis_dtype in [dtypes.int64, dtypes.int32]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): axis = constant_op.constant(0, axis_dtype) tf_out = math_ops.cumprod(x, axis).eval() @@ -235,7 +235,7 @@ class CumprodTest(test.TestCase): def testInvalidAxis(self): x = np.arange(0, 10).reshape([2, 5]).astype(np.float32) input_tensor = ops.convert_to_tensor(x) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesWithPredicateMatch( errors_impl.InvalidArgumentError, lambda e: "Expected scan axis in the range [-2, 2)" in str(e)): @@ -251,7 +251,7 @@ class CumprodTest(test.TestCase): def _compareGradient(self, shape, axis, exclusive, reverse): x = np.arange(1, 9).reshape(shape).astype(np.float64) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): t = ops.convert_to_tensor(x) result = math_ops.cumprod(t, axis, exclusive, reverse) jacob_t, jacob_n = gradient_checker.compute_gradient( diff --git a/tensorflow/python/kernel_tests/scatter_nd_ops_test.py b/tensorflow/python/kernel_tests/scatter_nd_ops_test.py index 9843bf4be0..54d542fb5f 100644 --- a/tensorflow/python/kernel_tests/scatter_nd_ops_test.py +++ b/tensorflow/python/kernel_tests/scatter_nd_ops_test.py @@ -102,7 +102,7 @@ class StatefulScatterNdTest(test.TestCase): np.random.seed(8) ref_shapes = [(3, 6), (3, 6), (3, 6, 9), (3, 6, 9), (3, 6, 9), (3, 6, 9)] indices_shapes = [(2,), (2, 2), (2,), (2, 2), (2, 3), (2, 3, 3)] - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): for ref_shape, indices_shape in zip(ref_shapes, indices_shapes): num_updates = indices_shape[0] ixdim = indices_shape[-1] @@ -158,7 +158,7 @@ class StatefulScatterNdTest(test.TestCase): scatter = state_ops.scatter_nd_update(ref, indices, updates) init = variables.global_variables_initializer() - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run(init) result = sess.run(scatter) self.assertAllClose(result, expected) @@ -172,7 +172,7 @@ class StatefulScatterNdTest(test.TestCase): scatter = state_ops.scatter_nd_update(ref, indices, updates) init = variables.global_variables_initializer() - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run(init) sess.run(scatter) self.assertAllClose(ref.eval(), expected) @@ -186,7 +186,7 @@ class StatefulScatterNdTest(test.TestCase): scatter = state_ops.scatter_nd_update(ref, indices, updates) init = variables.global_variables_initializer() - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run(init) result = sess.run(scatter) self.assertAllClose(result, expected) @@ -200,7 +200,7 @@ class StatefulScatterNdTest(test.TestCase): scatter = state_ops.scatter_nd_update(ref, indices, updates) init = variables.global_variables_initializer() - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: sess.run(init) result = sess.run(scatter) self.assertAllClose(result, expected) @@ -239,7 +239,7 @@ class StatefulScatterNdTest(test.TestCase): # TODO(simister): Re-enable once binary size increase due to # extra templating is back under control and this op is re-enabled # def testBooleanScatterUpdate(self): - # with self.test_session(use_gpu=False) as session: + # with self.session(use_gpu=False) as session: # var = tf.Variable([True, False]) # update0 = tf.scatter_nd_update(var, [[1]], [True]) # update1 = tf.scatter_nd_update( @@ -257,7 +257,7 @@ class StatefulScatterNdTest(test.TestCase): state_ops.scatter_nd_update): params = np.array([1, 2, 3, 4, 5, 6]).astype(np.float32) updates = np.array([-3, -4, -5]).astype(np.float32) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): ref = variables.VariableV1(params) ref.initializer.run() @@ -356,7 +356,7 @@ class StatefulScatterNdTest(test.TestCase): updates = np.array([-3, -4, -5]).astype(np.float32) # With GPU, the code ignores indices that are out of range. # We don't test the implementation; just test there's no failures. - with self.test_session(force_gpu=True): + with self.cached_session(force_gpu=True): ref = variables.Variable(params) ref.initializer.run() diff --git a/tensorflow/python/kernel_tests/scatter_ops_test.py b/tensorflow/python/kernel_tests/scatter_ops_test.py index 527b7daf10..87c345245c 100644 --- a/tensorflow/python/kernel_tests/scatter_ops_test.py +++ b/tensorflow/python/kernel_tests/scatter_ops_test.py @@ -133,7 +133,7 @@ class ScatterTest(test.TestCase): repeat_indices=False, updates_are_scalar=False): np.random.seed(8) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): for indices_shape in (), (2,), (3, 7), (3, 4, 7): for extra_shape in (), (5,), (5, 9): # Generate random indices with no duplicates for easy numpy comparison @@ -276,7 +276,7 @@ class ScatterTest(test.TestCase): def testBooleanScatterUpdate(self): if not test.is_gpu_available(): - with self.test_session(use_gpu=False) as session: + with self.session(use_gpu=False) as session: var = variables.Variable([True, False]) update0 = state_ops.scatter_update(var, 1, True) update1 = state_ops.scatter_update( @@ -293,7 +293,7 @@ class ScatterTest(test.TestCase): params = np.array([1, 2, 3, 4, 5, 6]).astype(np.float32) updates = np.array([-3, -4, -5]).astype(np.float32) if not test.is_gpu_available(): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): ref = variables.VariableV1(params) ref.initializer.run() @@ -320,7 +320,7 @@ class ScatterTest(test.TestCase): updates = np.array([-3, -4, -5]).astype(np.float32) # With GPU, the code ignores indices that are out of range. # We don't test the implementation; just test there's no failures. - with self.test_session(force_gpu=True): + with self.cached_session(force_gpu=True): ref = variables.Variable(params) ref.initializer.run() diff --git a/tensorflow/python/kernel_tests/segment_reduction_ops_test.py b/tensorflow/python/kernel_tests/segment_reduction_ops_test.py index 2931877c11..3f7e43b533 100644 --- a/tensorflow/python/kernel_tests/segment_reduction_ops_test.py +++ b/tensorflow/python/kernel_tests/segment_reduction_ops_test.py @@ -113,7 +113,7 @@ class SegmentReductionOpTest(SegmentReductionHelper): else: curr_ops_list = ops_list for use_gpu in [True, False]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_x, np_x = self._input(shape, dtype=dtype) for np_op1, np_op2, tf_op in curr_ops_list: np_ans = self._segmentReduce(indices, np_x, np_op1, np_op2) @@ -136,7 +136,7 @@ class SegmentReductionOpTest(SegmentReductionHelper): def testSegmentIdsSize(self): shape = [4, 4] for use_gpu in [True, False]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_x, _ = self._input(shape) indices = [0, 1] s = math_ops.segment_sum(data=tf_x, segment_ids=indices) @@ -147,7 +147,7 @@ class SegmentReductionOpTest(SegmentReductionHelper): # This is a baseline for the following SegmentIdsInvalid* tests. shape = [4, 4] for use_gpu in [True, False]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_x, _ = self._input(shape, dtype=dtypes_lib.float32) indices = [0, 0, 0, 1] result = math_ops.segment_sum(data=tf_x, segment_ids=indices).eval() @@ -156,7 +156,7 @@ class SegmentReductionOpTest(SegmentReductionHelper): def testSegmentIdsGreaterThanZero(self): shape = [4, 4] for use_gpu in [True, False]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_x, np_x = self._input(shape, dtype=dtypes_lib.float32) indices = [1, 1, 2, 2] np_ans = self._segmentReduce(indices, np_x, np.add) @@ -167,7 +167,7 @@ class SegmentReductionOpTest(SegmentReductionHelper): def testSegmentIdsHole(self): shape = [4, 4] for use_gpu in [True, False]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_x, np_x = self._input(shape, dtype=dtypes_lib.float32) indices = [0, 0, 3, 3] np_ans = self._segmentReduce(indices, np_x, np.add) @@ -209,7 +209,7 @@ class SegmentReductionOpTest(SegmentReductionHelper): def testSegmentIdsInvalid4(self): shape = [4, 4] for use_gpu in [True, False]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_x, _ = self._input(shape, dtype=dtypes_lib.float32) indices = [0, 0, 0, -1] s = math_ops.segment_sum(data=tf_x, segment_ids=indices) @@ -219,7 +219,7 @@ class SegmentReductionOpTest(SegmentReductionHelper): def testSegmentIdsInvalid5(self): shape = [4, 4] for use_gpu in [True, False]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_x, _ = self._input(shape, dtype=dtypes_lib.float32) indices = [0, 0, 0, -2] s = math_ops.segment_sum(data=tf_x, segment_ids=indices) @@ -284,7 +284,7 @@ class UnsortedSegmentTest(SegmentReductionHelper): ops_list = self.complex_ops_list if dtype.is_complex else self.ops_list tf_x, np_x = self._input(shape, dtype=dtype) for use_gpu in [True, False]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): for np_op1, np_op2, tf_op, init_op in ops_list: # sqrt_n doesn't support integers if (np_op2 == self._sqrt_n_reduce_op and dtype.is_integer): @@ -310,7 +310,7 @@ class UnsortedSegmentTest(SegmentReductionHelper): for indices in indices_flat, indices_flat.reshape(5, 2): shape = indices.shape + (2,) for dtype in dtypes: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_x, np_x = self._input(shape) num_segments_constant = constant_op.constant( num_segments, dtype=dtype) @@ -334,7 +334,7 @@ class UnsortedSegmentTest(SegmentReductionHelper): shape = indices.shape + (num_cols,) # test CPU and GPU as tf.gather behaves differently on each device for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): for _, _, tf_op, _ in ops_list: tf_x, np_x = self._input(shape, dtype=dtype) s = tf_op(tf_x, indices, num_segments) @@ -360,7 +360,7 @@ class UnsortedSegmentTest(SegmentReductionHelper): gradients_indices[range(9), indices] = [0, 0, 0, 4, 0, 0, 9, 9, 9] gradients_indices_neg[range(9), indices_neg] = [0, 1, 0, 0, 2, 2, 0, 3, 3] for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): for ind, grad_gt in [(indices, gradients_indices), (indices_neg, gradients_indices_neg)]: s = math_ops.unsorted_segment_prod(values_tf, @@ -382,7 +382,7 @@ class UnsortedSegmentTest(SegmentReductionHelper): shape = [n, num_cols] num_segments = max(indices) + 1 for dtype in self.differentiable_dtypes: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_x, np_x = self._input(shape, dtype=dtype) # Results from UnsortedSegmentSum unsorted_s = math_ops.unsorted_segment_sum( @@ -407,7 +407,7 @@ class UnsortedSegmentTest(SegmentReductionHelper): # Note: GPU kernel does not return the out-of-range error needed for this # test, so this test is marked as cpu-only. # Note: With PR #13055 a negative index will be ignored silently. - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for bad in [[2]], [[7]]: unsorted = math_ops.unsorted_segment_sum([[17]], bad, num_segments=2) with self.assertRaisesOpError( @@ -417,7 +417,7 @@ class UnsortedSegmentTest(SegmentReductionHelper): def testEmptySecondDimension(self): dtypes = [np.float16, np.float32, np.float64, np.int64, np.int32, np.complex64, np.complex128] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in dtypes: for itype in (np.int32, np.int64): data = np.zeros((2, 0), dtype=dtype) @@ -433,7 +433,7 @@ class UnsortedSegmentTest(SegmentReductionHelper): for indices in indices_flat, indices_flat.reshape(5, 2): shape = indices.shape + (2,) for dtype in self.all_dtypes: - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tf_x, np_x = self._input(shape, dtype=dtype) np_ans = self._segmentReduce( indices, np_x, np.add, op2=None, num_segments=num_segments) @@ -490,7 +490,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): segment_indices.append(i) num_indices = len(segment_indices) for dtype in dtypes: - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): tf_indices, np_indices, tf_x, np_x = self._sparse_input( shape, num_indices, dtype=dtype) for np_op1, np_op2, tf_op in ops_list: @@ -513,7 +513,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): self._mean_cum_op, self._mean_reduce_op, math_ops.sparse_segment_mean)] segment_indices = [0, 2, 2, 2] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for np_op1, np_op2, tf_op in ops_list: np_ans = self._sparseSegmentReduce(np_x, tf_indices, segment_indices, np_op1, np_op2) @@ -529,7 +529,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): segment_indices = [0, 2, 2, 2] tf_indices = [8, 3, 0, 9] num_segments = 5 - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for np_op1, np_op2, tf_op in ops_list: np_ans = self._sparseSegmentReduce( np_x, @@ -555,7 +555,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): segment_indices = [] tf_indices = [] num_segments = 5 - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op( data=tf_x, @@ -571,7 +571,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): self._mean_cum_op, self._mean_reduce_op, math_ops.sparse_segment_mean)] segment_indices = [1, 2, 2, 2] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for np_op1, np_op2, tf_op in ops_list: np_ans = self._sparseSegmentReduce(np_x, tf_indices, segment_indices, np_op1, np_op2) @@ -585,7 +585,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ops_list = [math_ops.sparse_segment_sum, math_ops.sparse_segment_mean] segment_indices = [0, 1, 2, 2] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(data=tf_x, indices=tf_indices, segment_ids=segment_indices) s.eval() @@ -595,7 +595,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ops_list = [math_ops.sparse_segment_sum, math_ops.sparse_segment_mean] segment_indices = [0, 1, 2, 2] tf_indices = [8, -1, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(data=tf_x, indices=tf_indices, segment_ids=segment_indices) with self.assertRaisesOpError( @@ -607,7 +607,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ops_list = [math_ops.sparse_segment_sum, math_ops.sparse_segment_mean] segment_indices = [0, 1, 2, 2] tf_indices = [8, 3, 0, 10] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(data=tf_x, indices=tf_indices, segment_ids=segment_indices) with self.assertRaisesOpError( @@ -619,7 +619,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ops_list = [math_ops.sparse_segment_sum, math_ops.sparse_segment_mean] segment_indices = [0, 1, 0, 1] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(data=tf_x, indices=tf_indices, segment_ids=segment_indices) with self.assertRaisesOpError("segment ids are not increasing"): @@ -630,7 +630,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ops_list = [math_ops.sparse_segment_sum, math_ops.sparse_segment_mean] segment_indices = [0, 1, 2, 0] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(data=tf_x, indices=tf_indices, segment_ids=segment_indices) with self.assertRaisesOpError( @@ -643,7 +643,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ops_list = [math_ops.sparse_segment_sum, math_ops.sparse_segment_mean] segment_indices = [-1, 0, 1, 1] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(data=tf_x, indices=tf_indices, segment_ids=segment_indices) with self.assertRaisesOpError( @@ -656,7 +656,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ops_list = [math_ops.sparse_segment_sum, math_ops.sparse_segment_mean] segment_indices = [0, 0, 0, -1] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(data=tf_x, indices=tf_indices, segment_ids=segment_indices) with self.assertRaisesOpError("segment ids must be >= 0"): @@ -667,7 +667,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ops_list = [math_ops.sparse_segment_sum, math_ops.sparse_segment_mean] segment_indices = [0, 0, 0, -2] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(data=tf_x, indices=tf_indices, segment_ids=segment_indices) with self.assertRaisesOpError("segment ids must be >= 0"): @@ -683,7 +683,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): num_segments = 5 segment_indices = [0, 1, 3, 3] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op( data=tf_x, @@ -701,7 +701,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): num_segments = 5 segment_indices = [0, 1, 3, 5] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op( data=tf_x, @@ -720,7 +720,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): num_segments = -2 segment_indices = [0, 1, 3, 3] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: with self.assertRaisesRegexp( ValueError, "Cannot specify a negative value for num_segments"): @@ -782,7 +782,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ] segment_indices = [0, 1, 2, 2] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(tf_x, tf_indices, segment_indices, 10) s.eval() @@ -794,7 +794,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ] segment_indices = [0, 1, 2, 2] tf_indices = [8, 3, 0, 10] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(tf_x, tf_indices, segment_indices, 10) with self.assertRaisesOpError(r"Index 10 out of range \[0, 10\)"): @@ -807,7 +807,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ] segment_indices = [0, 1, 2, 2] tf_indices = [8, 3, -1, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(tf_x, tf_indices, segment_indices, 10) with self.assertRaisesOpError(r"Index -1 out of range \[0, 10\)"): @@ -821,7 +821,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ] segment_indices = [0, 1, 1, 4] # 5 segments tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(tf_x, tf_indices, segment_indices, 10) with self.assertRaisesOpError("Invalid number of segments"): @@ -834,7 +834,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ] segment_indices = [0, 1, 2, 0] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(tf_x, tf_indices, segment_indices, 10) with self.assertRaisesOpError(r"Segment id 1 out of range \[0, 1\)"): @@ -847,7 +847,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ] segment_indices = [-1, 0, 1, 1] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(tf_x, tf_indices, segment_indices, 10) with self.assertRaisesOpError(r"Segment id -1 out of range \[0, 2\)"): @@ -860,7 +860,7 @@ class SparseSegmentReductionOpTest(SparseSegmentReductionHelper): ] segment_indices = [0, 1, 2, -1] tf_indices = [8, 3, 0, 9] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for tf_op in ops_list: s = tf_op(tf_x, tf_indices, segment_indices, 10) with self.assertRaisesOpError(r"Segment id 0 out of range \[0, 0\)"): diff --git a/tensorflow/python/kernel_tests/self_adjoint_eig_op_test.py b/tensorflow/python/kernel_tests/self_adjoint_eig_op_test.py index d2647088c5..1b4aff8c9c 100644 --- a/tensorflow/python/kernel_tests/self_adjoint_eig_op_test.py +++ b/tensorflow/python/kernel_tests/self_adjoint_eig_op_test.py @@ -51,7 +51,7 @@ class SelfAdjointEigTest(test.TestCase): def testConcurrentExecutesWithoutError(self): all_ops = [] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: for compute_v_ in True, False: matrix1 = random_ops.random_normal([5, 5], seed=42) matrix2 = random_ops.random_normal([5, 5], seed=42) @@ -80,7 +80,7 @@ class SelfAdjointEigTest(test.TestCase): "self_adjoint_eig_fail_if_denorms_flushed.txt")).astype(np.float32) self.assertEqual(matrix.shape, (32, 32)) matrix_tensor = constant_op.constant(matrix) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: (e, v) = sess.run(linalg_ops.self_adjoint_eig(matrix_tensor)) self.assertEqual(e.size, 32) self.assertAllClose( @@ -152,7 +152,7 @@ def _GetSelfAdjointEigTest(dtype_, shape_, compute_v_): else: atol = 1e-12 np_e, np_v = np.linalg.eigh(a) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): if compute_v_: tf_e, tf_v = linalg_ops.self_adjoint_eig(constant_op.constant(a)) @@ -201,7 +201,7 @@ def _GetSelfAdjointEigGradTest(dtype_, shape_, compute_v_): tol = 1e-2 else: tol = 1e-7 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tf_a = constant_op.constant(a) if compute_v_: tf_e, tf_v = linalg_ops.self_adjoint_eig(tf_a) diff --git a/tensorflow/python/kernel_tests/shape_ops_test.py b/tensorflow/python/kernel_tests/shape_ops_test.py index 0304dc3875..ee813e5ffd 100644 --- a/tensorflow/python/kernel_tests/shape_ops_test.py +++ b/tensorflow/python/kernel_tests/shape_ops_test.py @@ -50,7 +50,7 @@ class ShapeOpsTest(test.TestCase): def _compareShape(self, x, use_gpu=False): np_ans = np.array(np.shape(x)) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_ans = array_ops.shape(x) tf_ans_64 = array_ops.shape(x, out_type=dtypes.int64) result = tf_ans.eval() @@ -62,7 +62,7 @@ class ShapeOpsTest(test.TestCase): def _compareShapeSparse(self, x_np, use_gpu=False): np_ans = np.array(np.shape(x_np)) x_tf, unused_nnz = _sparsify(x_np) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_ans = array_ops.shape(x_tf) result = tf_ans.eval() self.assertAllEqual(np_ans, result) @@ -70,7 +70,7 @@ class ShapeOpsTest(test.TestCase): def _compareShapeN(self, x, use_gpu=False): np_ans = np.array(np.shape(x)) - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: tf_ans = array_ops.shape_n([x, x, x]) tf_ans_64 = array_ops.shape_n([x, x, x], out_type=dtypes.int64) result = sess.run(tf_ans) @@ -82,7 +82,7 @@ class ShapeOpsTest(test.TestCase): def _compareRank(self, x, use_gpu=False): np_ans = np.asarray(np.ndim(x)) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_ans = array_ops.rank(x) result = tf_ans.eval() self.assertAllEqual(np_ans, result) @@ -91,7 +91,7 @@ class ShapeOpsTest(test.TestCase): def _compareRankSparse(self, x_np, use_gpu=False): np_ans = np.asarray(np.ndim(x_np)) x_tf, unused_nnz = _sparsify(x_np) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_ans = array_ops.rank(x_tf) result = tf_ans.eval() self.assertAllEqual(np_ans, result) @@ -99,7 +99,7 @@ class ShapeOpsTest(test.TestCase): def _compareSize(self, x, use_gpu=False): np_ans = np.asarray(np.size(x)) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_ans = array_ops.size(x) result = tf_ans.eval() tf_ans_64 = array_ops.size(x, out_type=dtypes.int64) @@ -111,7 +111,7 @@ class ShapeOpsTest(test.TestCase): def _compareSizeSparse(self, x_np, use_gpu=False): np_ans = np.asarray(np.size(x_np)) x_tf, unused_nnz = _sparsify(x_np) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_ans = array_ops.size(x_tf) result = tf_ans.eval() self.assertAllEqual(np_ans, result) @@ -174,7 +174,7 @@ class ShapeOpsTest(test.TestCase): def _compareExpandDims(self, x, dim, use_gpu): np_ans = np.expand_dims(x, axis=dim) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tensor = array_ops.expand_dims(x, dim) tf_ans = tensor.eval() self.assertShapeEqual(np_ans, tensor) @@ -262,14 +262,14 @@ class ShapeOpsTest(test.TestCase): for dtype in [dtypes.int32, dtypes.int64]: x = np.zeros([2]) np_ans = np.expand_dims(x, axis=0) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tensor = array_ops.expand_dims(x, constant_op.constant(0, dtype)) tf_ans = tensor.eval() self.assertShapeEqual(np_ans, tensor) self.assertAllEqual(np_ans, tf_ans) def _compareSqueeze(self, x, squeeze_dims, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): if squeeze_dims: np_ans = np.squeeze(x, axis=tuple(squeeze_dims)) tensor = array_ops.squeeze(x, squeeze_dims) @@ -337,7 +337,7 @@ class ShapeOpsTest(test.TestCase): # Numpy squeezes a 1 element tensor into a zero dimensional tensor. # Verify that we do the same. for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tensor = array_ops.squeeze(np.zeros([1, 1, 1]), []) self.assertEqual(np.shape(1), tensor.get_shape()) tf_ans = tensor.eval() @@ -347,7 +347,7 @@ class ShapeOpsTest(test.TestCase): # Numpy squeezes a 1 element tensor into a zero dimensional tensor. # Verify that we do the same. for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tensor = array_ops.squeeze([[[False]]], []) self.assertEqual(np.shape(1), tensor.get_shape()) tf_ans = tensor.eval() @@ -355,7 +355,7 @@ class ShapeOpsTest(test.TestCase): def testSqueezeOnlyOnes(self): for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): input_1x1x3 = np.zeros([1, 1, 3]) self._compareSqueezeAll(input_1x1x3) self._compareSqueezeAll(input_1x1x3, [0]) @@ -364,7 +364,7 @@ class ShapeOpsTest(test.TestCase): def testSqueezeErrors(self): for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): self.assertRaises(ValueError, array_ops.squeeze, np.zeros([1, 2, 1]), [-4]) self.assertRaises(ValueError, array_ops.squeeze, @@ -412,7 +412,7 @@ class TileTest(test.TestCase): def testScalar(self): for use_gpu in False, True: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): a = constant_op.constant(7, shape=[], dtype=dtypes.float32) tiled = array_ops.tile(a, []) result = tiled.eval() @@ -423,7 +423,7 @@ class TileTest(test.TestCase): def testSimple(self): # multiples could be int32 or int64 for dtype in [dtypes.int32, dtypes.int64]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = np.random.rand(4, 1).astype(np.float32) a = constant_op.constant(inp) tiled = array_ops.tile(a, constant_op.constant([1, 4], dtype=dtype)) @@ -490,7 +490,7 @@ class TileTest(test.TestCase): bytes: (dtypes.string, bytes) } for dtype_np, (dtype_tf, cast) in types_to_test.items(): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = np.random.rand(4, 1).astype(dtype_np) a = constant_op.constant( [cast(x) for x in inp.ravel(order="C")], @@ -517,7 +517,7 @@ class TileTest(test.TestCase): array_ops.tile(a, [[2, 3], [3, 4]]).eval() def _RunAndVerifyResult(self, rank, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): # Random dims of given rank input_shape = np.random.randint(1, 4, size=rank) inp = np.random.rand(*input_shape).astype("f") @@ -580,7 +580,7 @@ class TileTest(test.TestCase): self.assertTrue((np.abs(expected - result) < 1e-3).all()) def testGradientSimpleReductionOnGPU(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): inp = np.random.rand(4, 1).astype("f") a = constant_op.constant( [float(x) for x in inp.flatten()], shape=[4, 1], dtype=dtypes.float32) @@ -594,7 +594,7 @@ class TileTest(test.TestCase): self.assertAllClose(np.sum(grad_inp, axis=1).reshape(4, 1), result, 1e-3) def testGradientStridedReductionOnGPU(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): inp = np.random.rand(4, 2).astype("f") a = constant_op.constant( [float(x) for x in inp.flatten()], shape=[4, 2], dtype=dtypes.float32) @@ -613,7 +613,7 @@ class TileTest(test.TestCase): def _RunAndVerifyGradientResult(self, input_shape, multiples): for use_gpu in False, True: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): # Random values inp = np.asarray(np.random.rand(*input_shape)) a = constant_op.constant(inp, dtype=dtypes.float64) diff --git a/tensorflow/python/kernel_tests/slice_op_test.py b/tensorflow/python/kernel_tests/slice_op_test.py index c08d3222b3..0e8c276ba9 100644 --- a/tensorflow/python/kernel_tests/slice_op_test.py +++ b/tensorflow/python/kernel_tests/slice_op_test.py @@ -35,7 +35,7 @@ class SliceTest(test.TestCase): def testEmpty(self): inp = np.random.rand(4, 4).astype("f") for k in xrange(4): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): a = constant_op.constant(inp, shape=[4, 4], dtype=dtypes.float32) slice_t = a[2, k:k] slice_val = slice_t.eval() @@ -44,14 +44,14 @@ class SliceTest(test.TestCase): def testInt32(self): inp = np.random.rand(4, 4).astype("i") for k in xrange(4): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): a = constant_op.constant(inp, shape=[4, 4], dtype=dtypes.int32) slice_t = a[2, k:k] slice_val = slice_t.eval() self.assertAllEqual(slice_val, inp[2, k:k]) def testInt64Slicing(self): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): a = constant_op.constant([0, 1, 2], dtype=dtypes.int64) # Slice using int64 Tensor. @@ -74,7 +74,7 @@ class SliceTest(test.TestCase): def testSelectAll(self): for _ in range(10): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = np.random.rand(4, 4, 4, 4).astype("f") a = constant_op.constant(inp, shape=[4, 4, 4, 4], dtype=dtypes.float32) @@ -88,7 +88,7 @@ class SliceTest(test.TestCase): def testSingleDimension(self): for _ in range(10): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = np.random.rand(10).astype("f") a = constant_op.constant(inp, shape=[10], dtype=dtypes.float32) @@ -134,7 +134,7 @@ class SliceTest(test.TestCase): sess.run([slice_t], feed_dict={input_t: input_val}) def _testSliceMatrixDim0(self, x, begin, size): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_ans = array_ops.slice(x, [begin, 0], [size, x.shape[1]]).eval() np_ans = x[begin:begin + size, :] self.assertAllEqual(tf_ans, np_ans) @@ -149,7 +149,7 @@ class SliceTest(test.TestCase): def testSingleElementAll(self): for _ in range(10): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = np.random.rand(4, 4).astype("f") a = constant_op.constant(inp, shape=[4, 4], dtype=dtypes.float32) @@ -159,7 +159,7 @@ class SliceTest(test.TestCase): self.assertAllEqual(slice_val, inp[x, 0:y]) def testSimple(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: inp = np.random.rand(4, 4).astype("f") a = constant_op.constant( [float(x) for x in inp.ravel(order="C")], @@ -174,7 +174,7 @@ class SliceTest(test.TestCase): self.assertEqual(slice2_val.shape, slice2_t.get_shape()) def testComplex(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): inp = np.random.rand(4, 10, 10, 4).astype("f") a = constant_op.constant(inp, dtype=dtypes.float32) @@ -191,7 +191,7 @@ class SliceTest(test.TestCase): # Random dims of rank 6 input_shape = np.random.randint(0, 20, size=6) inp = np.random.rand(*input_shape).astype("f") - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: a = constant_op.constant( [float(x) for x in inp.ravel(order="C")], shape=input_shape, @@ -230,7 +230,7 @@ class SliceTest(test.TestCase): def _testGradientSlice(self, input_shape, slice_begin, slice_size): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): num_inputs = np.prod(input_shape) num_grads = np.prod(slice_size) inp = np.random.rand(num_inputs).astype("f").reshape(input_shape) @@ -255,7 +255,7 @@ class SliceTest(test.TestCase): self.assertAllClose(np_ans, result) def _testGradientVariableSize(self): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp = constant_op.constant([1.0, 2.0, 3.0], name="in") out = array_ops.slice(inp, [1], [-1]) grad_actual = gradients_impl.gradients(out, inp)[0].eval() @@ -265,7 +265,7 @@ class SliceTest(test.TestCase): # Regression test for bug in slice. A low-level bug in Eigen was causing # incorrect results for negative indices in multi-dimensional tensors. # See b/114318298. - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: x = constant_op.constant([[1., 2., 3.], [4., 5., 6.], [7., 8., 7]]) loss1 = math_ops.reduce_sum(x[:-1, :-1] * 1.0) loss2 = math_ops.reduce_sum(x[:-1][:, :-1]) @@ -322,7 +322,7 @@ class SliceTest(test.TestCase): self.assertEqual([None, 2], c.get_shape().as_list()) def testSliceOfSlice(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): a = constant_op.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) b = a[1:, :] c = b[:-1, :] diff --git a/tensorflow/python/kernel_tests/softmax_op_test.py b/tensorflow/python/kernel_tests/softmax_op_test.py index 89f4697e5c..3218d00c66 100644 --- a/tensorflow/python/kernel_tests/softmax_op_test.py +++ b/tensorflow/python/kernel_tests/softmax_op_test.py @@ -59,7 +59,7 @@ class SoftmaxTest(test.TestCase): # this bug in future. name = "arbitrary" np_softmax = self._npSoftmax(np_features, dim=dim, log=log) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): if log: tf_softmax = nn_ops.log_softmax(np_features, axis=dim, name=name) else: @@ -111,7 +111,7 @@ class SoftmaxTest(test.TestCase): type = np.float64 # pylint: disable=redefined-builtin max = np.finfo(type).max # pylint: disable=redefined-builtin features = np.array([[1., 1., 1., 1.], [max, 1., 2., 3.]]).astype(type) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): tf_log_softmax = nn_ops.log_softmax(features) out = tf_log_softmax.eval() self.assertAllClose( @@ -230,7 +230,7 @@ class SoftmaxTest(test.TestCase): np_softmax = self._npSoftmax(ones) for use_gpu in [True, False]: - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: x = array_ops.placeholder(dtypes.float32) y = nn_ops.softmax(x) tf_softmax = sess.run(y, feed_dict={x: ones}) diff --git a/tensorflow/python/kernel_tests/softplus_op_test.py b/tensorflow/python/kernel_tests/softplus_op_test.py index 636ed4747e..50a8291ea8 100644 --- a/tensorflow/python/kernel_tests/softplus_op_test.py +++ b/tensorflow/python/kernel_tests/softplus_op_test.py @@ -37,7 +37,7 @@ class SoftplusTest(test.TestCase): def _testSoftplus(self, np_features, use_gpu=False): np_softplus = self._npSoftplus(np_features) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): softplus = nn_ops.softplus(np_features) tf_softplus = softplus.eval() self.assertAllCloseAccordingToType(np_softplus, tf_softplus) diff --git a/tensorflow/python/kernel_tests/softsign_op_test.py b/tensorflow/python/kernel_tests/softsign_op_test.py index 1b4db9fa46..ee2e2e0303 100644 --- a/tensorflow/python/kernel_tests/softsign_op_test.py +++ b/tensorflow/python/kernel_tests/softsign_op_test.py @@ -34,7 +34,7 @@ class SoftsignTest(test.TestCase): def _testSoftsign(self, np_features, use_gpu=False): np_softsign = self._npSoftsign(np_features) - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): softsign = nn_ops.softsign(np_features) tf_softsign = softsign.eval() self.assertAllClose(np_softsign, tf_softsign) diff --git a/tensorflow/python/kernel_tests/spacetobatch_op_test.py b/tensorflow/python/kernel_tests/spacetobatch_op_test.py index e267c05915..21134adf2c 100644 --- a/tensorflow/python/kernel_tests/spacetobatch_op_test.py +++ b/tensorflow/python/kernel_tests/spacetobatch_op_test.py @@ -100,7 +100,7 @@ class SpaceToBatchTest(test.TestCase, PythonOpImpl): """ def _testPad(self, inputs, paddings, block_size, outputs): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # outputs = space_to_batch(inputs) x_tf = self.space_to_batch( math_ops.to_float(inputs), paddings, block_size=block_size) @@ -190,7 +190,7 @@ class SpaceToBatchNDTest(test.TestCase): block_shape = np.array(block_shape) paddings = np.array(paddings).reshape((len(block_shape), 2)) for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): # outputs = space_to_batch(inputs) x_tf = array_ops.space_to_batch_nd( math_ops.to_float(inputs), block_shape, paddings) @@ -309,7 +309,7 @@ class SpaceToBatchSpaceToDepth(test.TestCase, PythonOpImpl): array_ops.space_to_depth( array_ops.transpose(x, [3, 1, 2, 0]), block_size=block_size), [3, 1, 2, 0]) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual(y1.eval(), y2.eval()) @@ -494,7 +494,7 @@ class SpaceToBatchGradientTest(test.TestCase, PythonOpImpl): # Check the gradients. def _checkGrad(self, x, paddings, block_size): assert 4 == x.ndim - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_x = ops.convert_to_tensor(x) tf_y = self.space_to_batch(tf_x, paddings, block_size) epsilon = 1e-5 diff --git a/tensorflow/python/kernel_tests/spacetodepth_op_test.py b/tensorflow/python/kernel_tests/spacetodepth_op_test.py index cd90d16aac..9bea1b952a 100644 --- a/tensorflow/python/kernel_tests/spacetodepth_op_test.py +++ b/tensorflow/python/kernel_tests/spacetodepth_op_test.py @@ -36,12 +36,12 @@ class SpaceToDepthTest(test.TestCase): def _testOne(self, inputs, block_size, outputs, dtype=dtypes.float32): input_nhwc = math_ops.cast(inputs, dtype) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): # test NHWC (default) on CPU x_tf = array_ops.space_to_depth(input_nhwc, block_size) self.assertAllEqual(x_tf.eval(), outputs) if test.is_gpu_available(): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # test NHWC (default) on GPU x_tf = array_ops.space_to_depth(input_nhwc, block_size) self.assertAllEqual(x_tf.eval(), outputs) @@ -138,13 +138,13 @@ class SpaceToDepthTest(test.TestCase): input_nhwc = array_ops.ones([batch_size, 4, 6, 3]) x_out = array_ops.ones([batch_size, 2, 3, 12]) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): # test NHWC (default) on CPU x_tf = array_ops.space_to_depth(input_nhwc, block_size) self.assertAllEqual(x_tf.shape, x_out.shape) x_tf.eval() if test.is_gpu_available(): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # test NHWC (default) on GPU x_tf = array_ops.space_to_depth(input_nhwc, block_size) self.assertAllEqual(x_tf.shape, x_out.shape) @@ -274,7 +274,7 @@ class SpaceToDepthTest(test.TestCase): expected = self.spaceToDepthUsingTranspose(t, block_size, data_format) actual = array_ops.space_to_depth(t, block_size, data_format=data_format) - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: actual_vals, expected_vals = sess.run([actual, expected]) self.assertTrue(np.array_equal(actual_vals, expected_vals)) @@ -307,7 +307,7 @@ class SpaceToDepthGradientTest(test.TestCase): return assert 4 == x.ndim - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_x = ops.convert_to_tensor(x) tf_y = array_ops.space_to_depth(tf_x, block_size, data_format=data_format) epsilon = 1e-2 diff --git a/tensorflow/python/kernel_tests/sparse_add_op_test.py b/tensorflow/python/kernel_tests/sparse_add_op_test.py index 7371ebe389..a746830afb 100644 --- a/tensorflow/python/kernel_tests/sparse_add_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_add_op_test.py @@ -85,7 +85,7 @@ class SparseAddTest(test.TestCase): constant_op.constant(shape, dtypes.int64)) def testAddSelf(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: for sp_a in (self._SparseTensorValue_3x3(), self._SparseTensor_3x3()): for sp_b in (self._SparseTensorValue_3x3(), self._SparseTensor_3x3()): sp_sum = sparse_ops.sparse_add(sp_a, sp_b) @@ -99,7 +99,7 @@ class SparseAddTest(test.TestCase): self.assertAllEqual(sum_out.dense_shape, [3, 3]) def testAddSelfAndNegation(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_a = self._SparseTensor_3x3() sp_b = self._SparseTensor_3x3(negate=True) @@ -112,7 +112,7 @@ class SparseAddTest(test.TestCase): self.assertAllEqual(sum_out.dense_shape, [3, 3]) def testSmallValuesShouldVanish(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_a = self._SparseTensor_3x3() sp_b = self._SparseTensor_3x3_v2() @@ -141,7 +141,7 @@ class SparseAddTest(test.TestCase): def testGradients(self): np.random.seed(1618) # Make it reproducible. - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for n in [10, 31]: for m in [4, 17]: sp_a, nnz_a = self._randomTensor([n, m], np.float32) @@ -162,7 +162,7 @@ class SparseAddTest(test.TestCase): rand_vals_np = np.random.randn(n, m).astype(dtype) dense_np = np.random.randn(n, m).astype(dtype) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): sparse, unused_nnz = _sparsify(rand_vals_np, index_dtype=index_dtype) s = sparse_ops.sparse_add(sparse, constant_op.constant(dense_np)).eval() @@ -181,7 +181,7 @@ class SparseAddTest(test.TestCase): rand_vals_np = np.random.randn(n, m).astype(np.float32) dense_np = np.random.randn(n, m).astype(np.float32) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sparse, nnz = _sparsify(rand_vals_np) dense = constant_op.constant(dense_np, dtype=dtypes.float32) s = sparse_ops.sparse_add(sparse, dense) @@ -191,7 +191,7 @@ class SparseAddTest(test.TestCase): self.assertLess(err, 1e-3) def testInvalidSparseTensor(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: shape = [2, 2] val = [0] dense = constant_op.constant(np.zeros(shape, dtype=np.int32)) diff --git a/tensorflow/python/kernel_tests/sparse_concat_op_test.py b/tensorflow/python/kernel_tests/sparse_concat_op_test.py index d3c7983128..402c5eb4ea 100644 --- a/tensorflow/python/kernel_tests/sparse_concat_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_concat_op_test.py @@ -132,7 +132,7 @@ class SparseConcatTest(test.TestCase): constant_op.constant(shape, dtypes.int64)) def testConcat1(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: # concat(A): # [ 1] # [2 ] @@ -155,7 +155,7 @@ class SparseConcatTest(test.TestCase): self.assertAllEqual(concat_out.dense_shape, [3, 3]) def testConcat2(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: # concat(A, B): # [ 1 ] # [2 1 ] @@ -178,7 +178,7 @@ class SparseConcatTest(test.TestCase): self.assertAllEqual(concat_out.dense_shape, [3, 8]) def testConcatDim0(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: # concat(A, D): # [ 1] # [2 ] @@ -204,7 +204,7 @@ class SparseConcatTest(test.TestCase): self.assertAllEqual(concat_out.dense_shape, np.array([5, 3])) def testConcat3(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: # concat(A, B, C): # [ 1 ] # [2 1 1 ] @@ -229,7 +229,7 @@ class SparseConcatTest(test.TestCase): self.assertAllEqual(concat_out.dense_shape, [3, 10]) def testConcatNonNumeric(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: # concat(A, B): # [ a ] # [b e ] @@ -254,7 +254,7 @@ class SparseConcatTest(test.TestCase): self.assertAllEqual(concat_out.dense_shape, [3, 8]) def testMismatchedRank(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_a = self._SparseTensor_3x3() sp_e = self._SparseTensor_2x3x4() @@ -264,7 +264,7 @@ class SparseConcatTest(test.TestCase): sparse_ops.sparse_concat(concat_dim, [sp_a, sp_e]) def testMismatchedRankExpandNonconcatDim(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_a = self._SparseTensor_3x3() sp_e = self._SparseTensor_2x3x4() @@ -276,7 +276,7 @@ class SparseConcatTest(test.TestCase): concat_dim, [sp_a, sp_e], expand_nonconcat_dim=True) def testMismatchedShapes(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_a = self._SparseTensor_3x3() sp_b = self._SparseTensor_3x5() sp_c = self._SparseTensor_3x2() @@ -290,7 +290,7 @@ class SparseConcatTest(test.TestCase): sess.run(sp_concat) def testMismatchedShapesExpandNonconcatDim(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_a = self._SparseTensor_3x3() sp_b = self._SparseTensor_3x5() sp_c = self._SparseTensor_3x2() @@ -322,7 +322,7 @@ class SparseConcatTest(test.TestCase): self.assertAllEqual(sp_concat_dim1_out.dense_shape, [3, 13]) def testShapeInferenceUnknownShapes(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_inputs = [ self._SparseTensor_UnknownShape(), self._SparseTensor_UnknownShape(val_shape=[3]), diff --git a/tensorflow/python/kernel_tests/sparse_matmul_op_test.py b/tensorflow/python/kernel_tests/sparse_matmul_op_test.py index 90009fc33e..541463e76b 100644 --- a/tensorflow/python/kernel_tests/sparse_matmul_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_matmul_op_test.py @@ -48,7 +48,7 @@ class SparseMatMulTest(test.TestCase): sp_b=False, x_dtype=dtypes.float32, y_dtype=dtypes.float32): - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): tf_x = math_ops.cast(x, x_dtype) tf_y = math_ops.cast(y, y_dtype) tf_ans = math_ops.matmul( diff --git a/tensorflow/python/kernel_tests/sparse_ops_test.py b/tensorflow/python/kernel_tests/sparse_ops_test.py index 79efee3f5b..a45ce2e13b 100644 --- a/tensorflow/python/kernel_tests/sparse_ops_test.py +++ b/tensorflow/python/kernel_tests/sparse_ops_test.py @@ -72,7 +72,7 @@ class SparseToIndicatorTest(test_util.TensorFlowTestCase): constant_op.constant(shape, dtypes.int64)) def testInt32(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_5x6(dtypes.int32) output = sparse_ops.sparse_to_indicator(sp_input, 50).eval() @@ -84,7 +84,7 @@ class SparseToIndicatorTest(test_util.TensorFlowTestCase): self.assertAllEqual(output, expected_output) def testInt64(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_5x6(dtypes.int64) output = sparse_ops.sparse_to_indicator(sp_input, 50).eval() @@ -96,7 +96,7 @@ class SparseToIndicatorTest(test_util.TensorFlowTestCase): self.assertAllEqual(output, expected_output) def testHigherRank(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_2x3x4(dtypes.int64) output = sparse_ops.sparse_to_indicator(sp_input, 200).eval() @@ -147,7 +147,7 @@ class SparseMergeTest(test_util.TensorFlowTestCase): def testInt32AndFloat32(self): vocab_size = 50 indices_v, values_v = self._SparseTensorValue_3x50(np.int32, np.float32) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: for indices in (indices_v, sparse_tensor.SparseTensor.from_value(indices_v)): for values in (values_v, @@ -159,7 +159,7 @@ class SparseMergeTest(test_util.TensorFlowTestCase): def testInt64AndFloat32(self): vocab_size = 50 - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices, values = self._SparseTensor_3x50(np.int64, np.float32) sp_output = sparse_ops.sparse_merge(indices, values, vocab_size) @@ -168,7 +168,7 @@ class SparseMergeTest(test_util.TensorFlowTestCase): def testInt64AndFloat64(self): vocab_size = 50 - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices, values = self._SparseTensor_3x50(np.int64, np.float64) sp_output = sparse_ops.sparse_merge(indices, values, vocab_size) @@ -177,7 +177,7 @@ class SparseMergeTest(test_util.TensorFlowTestCase): def testInt32AndFloat32NonCanonicalOrder(self): vocab_size = 50 - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices, values = self._SparseTensor_3x50(np.int32, np.float32) sp_output = sparse_ops.sparse_merge( indices, values, vocab_size, already_sorted=True) @@ -187,7 +187,7 @@ class SparseMergeTest(test_util.TensorFlowTestCase): def testInt64AndFloat32NonCanonicalOrder(self): vocab_size = 50 - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices, values = self._SparseTensor_3x50(np.int64, np.float32) sp_output = sparse_ops.sparse_merge( indices, values, vocab_size, already_sorted=True) @@ -198,7 +198,7 @@ class SparseMergeTest(test_util.TensorFlowTestCase): def testInt64AndFloat64NonCanonicalOrder(self): vocab_size = 50 vocab_size_tensor = constant_op.constant(vocab_size, dtypes.int64) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices, values = self._SparseTensor_3x50(np.int64, np.float64) sp_output = sparse_ops.sparse_merge( indices, values, vocab_size_tensor, already_sorted=True) @@ -257,7 +257,7 @@ class SparseMergeHighDimTest(test_util.TensorFlowTestCase): def testInt64AndFloat32(self): vocab_size = [50, 31] - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices, values = self._SparseTensor_3x50(np.int64, np.float32) sp_output = sparse_ops.sparse_merge(indices, values, vocab_size) @@ -266,7 +266,7 @@ class SparseMergeHighDimTest(test_util.TensorFlowTestCase): def testInt64AndFloat64(self): vocab_size = [50, 31] - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices, values = self._SparseTensor_3x50(np.int64, np.float64) sp_output = sparse_ops.sparse_merge(indices, values, vocab_size) @@ -275,7 +275,7 @@ class SparseMergeHighDimTest(test_util.TensorFlowTestCase): def testInt64AndFloat64Shape(self): vocab_size = [50, 30] - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices, values = self._SparseTensor_3x50(np.int64, np.float64) sp_output = sparse_ops.sparse_merge(indices, values, vocab_size) @@ -297,7 +297,7 @@ class SparseRetainTest(test_util.TensorFlowTestCase): return sparse_tensor.SparseTensor.from_value(self._SparseTensorValue_5x6()) def testBasic(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: for sp_input in (self._SparseTensorValue_5x6(), self._SparseTensor_5x6()): to_retain = np.array([1, 0, 0, 1, 1, 0], dtype=np.bool) sp_output = sparse_ops.sparse_retain(sp_input, to_retain) @@ -309,7 +309,7 @@ class SparseRetainTest(test_util.TensorFlowTestCase): self.assertAllEqual(output.dense_shape, [5, 6]) def testRetainNone(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensor_5x6() to_retain = np.zeros((6,), dtype=np.bool) sp_output = sparse_ops.sparse_retain(sp_input, to_retain) @@ -321,7 +321,7 @@ class SparseRetainTest(test_util.TensorFlowTestCase): self.assertAllEqual(output.dense_shape, [5, 6]) def testMismatchedRetainShape(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_5x6() to_retain = np.array([1, 0, 0, 1, 0], dtype=np.bool) with self.assertRaises(ValueError): @@ -360,7 +360,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): self.assertAllEqual([3, 6, 7], sp_output.get_shape()) def testBasic(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensor_2x5x6() new_shape = np.array([3, 6, 7], dtype=np.int64) sp_output = sparse_ops.sparse_reset_shape(sp_input, new_shape) @@ -373,7 +373,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): self.assertAllEqual(output.dense_shape, [3, 6, 7]) def testInputUnavailableInGraphConstructionOk(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorValue_2x5x6() new_shape = np.array([3, 6, 7], dtype=np.int64) sp_output = sparse_ops.sparse_reset_shape(sp_input, new_shape) @@ -386,7 +386,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): self.assertAllEqual(output.dense_shape, [3, 6, 7]) def testFeedInputUnavailableInGraphConstructionOk(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = array_ops.sparse_placeholder(dtype=dtypes.int32) new_shape = np.array([3, 6, 7], dtype=np.int64) sp_output = sparse_ops.sparse_reset_shape(sp_input, new_shape) @@ -400,7 +400,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): self.assertAllEqual(output.dense_shape, [3, 6, 7]) def testTightBoundingBox(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensor_2x5x6() sp_output = sparse_ops.sparse_reset_shape(sp_input) @@ -412,7 +412,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): self.assertAllEqual(output.dense_shape, [2, 4, 5]) def testTightBoundingBoxEmpty(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensor_2x5x6_Empty() sp_output = sparse_ops.sparse_reset_shape(sp_input) @@ -423,7 +423,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): self.assertAllEqual(output.dense_shape, [0, 0, 0]) def testInvalidRank(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_2x5x6() new_shape = np.array([3, 7], dtype=np.int64) @@ -431,7 +431,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): sparse_ops.sparse_reset_shape(sp_input, new_shape) def testInvalidRankNewShapeUnavailableInGraphConstruction(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: new_shape = array_ops.placeholder(dtype=dtypes.int64) sp_input = self._SparseTensor_2x5x6() out = sparse_ops.sparse_reset_shape(sp_input, new_shape) @@ -447,7 +447,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): sparse_ops.sparse_reset_shape(sp_input, new_shape) def testInvalidDimensionSizeDynamic(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensor_2x5x6() new_shape = array_ops.placeholder(dtype=dtypes.int32) out = sparse_ops.sparse_reset_shape(sp_input, new_shape) @@ -457,7 +457,7 @@ class SparseResetShapeTest(test_util.TensorFlowTestCase): def testInvalidDimensionSizeInputUnavailableInGraphConstruction(self): sp_input = array_ops.sparse_placeholder(dtype=dtypes.int32) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: new_shape = np.array([3, 7, 5], dtype=np.int64) out = sparse_ops.sparse_reset_shape(sp_input, new_shape) @@ -497,7 +497,7 @@ class SparseFillEmptyRowsTest(test_util.TensorFlowTestCase): constant_op.constant(shape, dtypes.int64)) def testFillNumber(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: for sp_input in (self._SparseTensorValue_5x6(), self._SparseTensor_5x6()): sp_output, empty_row_indicator = ( sparse_ops.sparse_fill_empty_rows(sp_input, -1)) @@ -514,7 +514,7 @@ class SparseFillEmptyRowsTest(test_util.TensorFlowTestCase): np.array([0, 0, 1, 0, 1]).astype(np.bool)) def testFillFloat(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: values = constant_op.constant( [0.0, 10.0, 13.0, 14.0, 32.0, 33.0], dtype=dtypes.float64) default_value = constant_op.constant(-1.0, dtype=dtypes.float64) @@ -548,7 +548,7 @@ class SparseFillEmptyRowsTest(test_util.TensorFlowTestCase): self.assertLess(default_value_grad_err, 1e-8) def testFillString(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensor_String5x6() sp_output, empty_row_indicator = ( sparse_ops.sparse_fill_empty_rows(sp_input, "")) @@ -566,7 +566,7 @@ class SparseFillEmptyRowsTest(test_util.TensorFlowTestCase): np.array([0, 0, 1, 0, 1]).astype(np.bool)) def testNoEmptyRows(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensor_2x6() sp_output, empty_row_indicator = ( sparse_ops.sparse_fill_empty_rows(sp_input, -1)) @@ -590,7 +590,7 @@ class SparseAddTest(test_util.TensorFlowTestCase): sp_input = sparse_tensor.SparseTensor(indices, values, shape) sp_output = sparse_ops.sparse_add(sp_input, sp_input) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sess.run(variables.global_variables_initializer()) output = sess.run(sp_output) self.assertAllEqual(output.values, [2]) @@ -663,7 +663,7 @@ class SparseReduceTest(test_util.TensorFlowTestCase): sp_t = sparse_tensor.SparseTensor(self.ind, self.vals, self.dense_shape) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): self._compare_all(sp_t, None, ndims=2) self._compare_all(sp_t, 0, ndims=2) self._compare_all(sp_t, [1], ndims=2) @@ -674,7 +674,7 @@ class SparseReduceTest(test_util.TensorFlowTestCase): np.random.seed(1618) test_dims = [(1618, 1, 11, 7, 1), (1,), (1, 1, 1)] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for dims in test_dims: sp_t, unused_nnz = _sparsify(np.random.randn(*dims)) # reduce all using None @@ -686,7 +686,7 @@ class SparseReduceTest(test_util.TensorFlowTestCase): def testInvalidAxes(self): sp_t = sparse_tensor.SparseTensor(self.ind, self.vals, self.dense_shape) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): with self.assertRaisesOpError("Invalid reduction dimension -3"): sparse_ops.sparse_reduce_sum(sp_t, -3).eval() with self.assertRaisesOpError("Invalid reduction dimension 2"): @@ -702,7 +702,7 @@ class SparseReduceTest(test_util.TensorFlowTestCase): np.random.seed(8161) test_dims = [(11, 1, 5, 7, 1), (2, 2)] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for dims in test_dims: sp_t, nnz = _sparsify(np.random.randn(*dims)) # reduce random axes from 1D to N-D @@ -742,7 +742,7 @@ class SparseMathOpsTest(test_util.TensorFlowTestCase): sp_shapes = [(10, 10, 10), (5, 5), (1618,), (3, 3, 7)] dense_shapes = [(10, 10, 1), (5, 5), (1,), (1, 7)] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for dtype in [np.float32, np.float64, np.int32, np.int64]: for sp_shape, dense_shape in zip(sp_shapes, dense_shapes): sp_vals_np = np.random.rand(*sp_shape).astype(dtype) + 1 @@ -761,7 +761,7 @@ class SparseMathOpsTest(test_util.TensorFlowTestCase): self.assertEqual(res.values.eval().dtype, np.float64) def testCwiseAdd(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): # Identity(2) + AllOnes(2,2). Should be equal to 2 * Identity(2). indices = [[0, 0], [1, 1]] vals = [1, 1] @@ -784,7 +784,7 @@ class SparseMathOpsTest(test_util.TensorFlowTestCase): sp_shapes = [(10, 10, 10), (5, 5), (1618,), (3, 3, 7)] dense_shapes = [(10, 10, 1), (5, 5), (1,), (1, 7)] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for dtype in [np.float32, np.float64]: for sp_shape, dense_shape in zip(sp_shapes, dense_shapes): sp_vals_np = np.random.rand(*sp_shape).astype(dtype) + 1 @@ -822,7 +822,7 @@ class SparseSoftmaxTest(test_util.TensorFlowTestCase): batched_sp_t, unused_nnz1 = _sparsify( sp_vals_np.reshape((1, n, m)), thresh=0.) # No masking. - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): densified = constant_op.constant(sp_vals_np) sp_result = sparse_ops.sparse_softmax(batched_sp_t).eval( @@ -853,7 +853,7 @@ class SparseSoftmaxTest(test_util.TensorFlowTestCase): sp_t, unused_nnz = _sparsify(values, thresh=1e-2) expected_values = [1., 1., 1., .5, .5] - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): result = sparse_ops.sparse_softmax(sp_t).eval() self.assertAllEqual(expected_values, result.values) @@ -862,7 +862,7 @@ class SparseSoftmaxTest(test_util.TensorFlowTestCase): def testGradient(self): x_shape = [2, 5, 10] - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): for dtype in [np.float32, np.float64]: x_np = np.random.randn(*x_shape).astype(dtype) x_tf, nnz = _sparsify(x_np) @@ -880,7 +880,7 @@ class SparseMinimumMaximumTest(test_util.TensorFlowTestCase): self.assertAllEqual(a.dense_shape, b.dense_shape) def testBasic(self): - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): # 1-D, values at index 0. sp_zero = sparse_tensor.SparseTensor([[0]], [0], [7]) sp_one = sparse_tensor.SparseTensor([[0]], [1], [7]) @@ -908,7 +908,7 @@ class SparseMinimumMaximumTest(test_util.TensorFlowTestCase): sp_a, unused_a_nnz = _sparsify(a_np, thresh=-.5) sp_b, unused_b_nnz = _sparsify(b_np, thresh=-.5) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): maximum_tf = sparse_ops.sparse_maximum(sp_a, sp_b) maximum_tf_densified = sparse_ops.sparse_tensor_to_dense( maximum_tf).eval() @@ -925,7 +925,7 @@ class SparseMinimumMaximumTest(test_util.TensorFlowTestCase): np.minimum(a_densified, b_densified), minimum_tf_densified) def testMismatchedShapes(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_zero = sparse_tensor.SparseTensor([[0, 0]], [0], [1, 1]) sp_one = sparse_tensor.SparseTensor([[0]], [1], [2]) with self.assertRaisesOpError("Operands do not have the same ranks"): @@ -943,7 +943,7 @@ class SparseTransposeTest(test.TestCase): if np.__version__ == "1.13.0": self.skipTest("numpy 1.13.0 bug") - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): np.random.seed(1618) shapes = [np.random.randint(1, 10, size=rank) for rank in range(1, 6)] for shape in shapes: diff --git a/tensorflow/python/kernel_tests/sparse_reorder_op_test.py b/tensorflow/python/kernel_tests/sparse_reorder_op_test.py index 18335d665a..7b83ae5177 100644 --- a/tensorflow/python/kernel_tests/sparse_reorder_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_reorder_op_test.py @@ -56,7 +56,7 @@ class SparseReorderTest(test.TestCase): self.assertAllEqual((5, 6), sp_output.get_shape()) def testAlreadyInOrder(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: input_val = self._SparseTensorValue_5x6(np.arange(6)) sp_output = sparse_ops.sparse_reorder(input_val) @@ -66,7 +66,7 @@ class SparseReorderTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, input_val.dense_shape) def testFeedAlreadyInOrder(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6(np.arange(6)) sp_output = sparse_ops.sparse_reorder(sp_input) @@ -78,7 +78,7 @@ class SparseReorderTest(test.TestCase): def testOutOfOrder(self): expected_output_val = self._SparseTensorValue_5x6(np.arange(6)) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: for _ in range(5): # To test various random permutations input_val = self._SparseTensorValue_5x6(np.random.permutation(6)) sp_output = sparse_ops.sparse_reorder(input_val) @@ -91,7 +91,7 @@ class SparseReorderTest(test.TestCase): def testFeedOutOfOrder(self): expected_output_val = self._SparseTensorValue_5x6(np.arange(6)) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: for _ in range(5): # To test various random permutations sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6(np.random.permutation(6)) @@ -104,7 +104,7 @@ class SparseReorderTest(test.TestCase): expected_output_val.dense_shape) def testGradients(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for _ in range(5): # To test various random permutations input_val = self._SparseTensorValue_5x6(np.random.permutation(6)) sp_input = sparse_tensor.SparseTensor(input_val.indices, diff --git a/tensorflow/python/kernel_tests/sparse_reshape_op_test.py b/tensorflow/python/kernel_tests/sparse_reshape_op_test.py index 89a54c8ab6..f7be397c33 100644 --- a/tensorflow/python/kernel_tests/sparse_reshape_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_reshape_op_test.py @@ -77,7 +77,7 @@ class SparseReshapeTest(test.TestCase): sparse_ops.sparse_reshape(sp_input, shape=(-1, 7)) def testSameShape(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(input_val, [5, 6]) @@ -87,7 +87,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, input_val.dense_shape) def testFeedSameShape(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [5, 6]) @@ -98,7 +98,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, input_val.dense_shape) def testWorksWellWithTfShape(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() shape = array_ops.shape(sp_input) # tf.shape generates int32 output @@ -110,7 +110,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, input_val.dense_shape) def testFeedSameShapeWithInferredDim(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [-1, 6]) @@ -121,7 +121,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, input_val.dense_shape) def testFeedNewShapeSameRank(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [3, 10]) @@ -134,7 +134,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, [3, 10]) def testFeedNewShapeSameRankWithInferredDim(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [3, -1]) @@ -147,7 +147,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, [3, 10]) def testUpRank(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(input_val, [2, 3, 5]) @@ -159,7 +159,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, [2, 3, 5]) def testFeedUpRank(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [2, 3, 5]) @@ -172,7 +172,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, [2, 3, 5]) def testFeedUpRankWithInferredDim(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [2, -1, 5]) @@ -185,7 +185,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, [2, 3, 5]) def testFeedDownRank(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_2x3x4() sp_output = sparse_ops.sparse_reshape(sp_input, [6, 4]) @@ -198,7 +198,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, [6, 4]) def testFeedDownRankWithInferredDim(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_2x3x4() sp_output = sparse_ops.sparse_reshape(sp_input, [6, -1]) @@ -211,7 +211,7 @@ class SparseReshapeTest(test.TestCase): self.assertAllEqual(output_val.dense_shape, [6, 4]) def testFeedMultipleInferredDims(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [4, -1, -1]) @@ -225,7 +225,7 @@ class SparseReshapeTest(test.TestCase): sparse_ops.sparse_reshape(sp_input, [4, 7]) def testFeedMismatchedSizes(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [4, 7]) @@ -234,7 +234,7 @@ class SparseReshapeTest(test.TestCase): sess.run(sp_output, {sp_input: input_val}) def testFeedMismatchedSizesWithInferredDim(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input_val = self._SparseTensorValue_5x6() sp_output = sparse_ops.sparse_reshape(sp_input, [4, -1]) @@ -242,7 +242,7 @@ class SparseReshapeTest(test.TestCase): sess.run(sp_output, {sp_input: input_val}) def testFeedPartialShapes(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): # Incorporate new rank into shape information if known sp_input = self._SparseTensorPlaceholder() sp_output = sparse_ops.sparse_reshape(sp_input, [2, 3, 5]) @@ -267,7 +267,7 @@ class SparseReshapeTest(test.TestCase): self.assertListEqual(sp_output.dense_shape.get_shape().as_list(), [None]) def testFeedDenseReshapeSemantics(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: # Compute a random rank-5 initial shape and new shape, randomly sparsify # it, and check that the output of SparseReshape has the same semantics # as a dense reshape. diff --git a/tensorflow/python/kernel_tests/sparse_serialization_ops_test.py b/tensorflow/python/kernel_tests/sparse_serialization_ops_test.py index 3847cebc7d..b24a086969 100644 --- a/tensorflow/python/kernel_tests/sparse_serialization_ops_test.py +++ b/tensorflow/python/kernel_tests/sparse_serialization_ops_test.py @@ -68,7 +68,7 @@ class SerializeSparseTest(test.TestCase): serialize_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: sp_input = self._SparseTensorValue_5x6(np.arange(6)) serialized = serialize_fn(sp_input, out_type=out_type) sp_deserialized = deserialize_fn(serialized, dtype=dtypes.int32) @@ -92,7 +92,7 @@ class SerializeSparseTest(test.TestCase): serialize_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: sp_input = self._SparseTensorValue_5x6(np.arange(6)) serialized = serialize_fn(sp_input, out_type=out_type) serialized = array_ops.stack([serialized, serialized]) @@ -125,7 +125,7 @@ class SerializeSparseTest(test.TestCase): def _testSerializeDeserializeBatchInconsistentShapeHelper( self, serialize_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: sp_input0 = self._SparseTensorValue_5x6(np.arange(6)) sp_input1 = self._SparseTensorValue_3x4(np.arange(6)) serialized0 = serialize_fn(sp_input0, out_type=out_type) @@ -158,7 +158,7 @@ class SerializeSparseTest(test.TestCase): serialize_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: sp_input = self._SparseTensorValue_5x6(np.arange(6)) serialized = serialize_fn(sp_input, out_type=out_type) serialized = array_ops.stack([serialized, serialized]) @@ -201,7 +201,7 @@ class SerializeSparseTest(test.TestCase): serialize_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: sp_input0 = self._SparseTensorPlaceholder() sp_input1 = self._SparseTensorPlaceholder() input0_val = self._SparseTensorValue_5x6(np.arange(6)) @@ -240,7 +240,7 @@ class SerializeSparseTest(test.TestCase): def _testSerializeManyShapeHelper(self, serialize_many_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: # N == 4 because shape_value == [4, 5] indices_value = np.array([[0, 0], [0, 1], [2, 0]], dtype=np.int64) values_value = np.array([b"a", b"b", b"c"]) @@ -268,7 +268,7 @@ class SerializeSparseTest(test.TestCase): serialize_many_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: # N == 4 because shape_value == [4, 5] indices_value = np.array([[0, 0], [0, 1], [2, 0]], dtype=np.int64) values_value = np.array([b"a", b"b", b"c"]) @@ -301,7 +301,7 @@ class SerializeSparseTest(test.TestCase): dtypes.variant) def testVariantSerializeDeserializeScalar(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices_value = np.array([[]], dtype=np.int64) values_value = np.array([37], dtype=np.int32) shape_value = np.array([], dtype=np.int64) @@ -322,7 +322,7 @@ class SerializeSparseTest(test.TestCase): self.assertAllEqual(deserialized_value.dense_shape, shape_value) def testVariantSerializeDeserializeScalarBatch(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: indices_value = np.array([[]], dtype=np.int64) values_value = np.array([37], dtype=np.int32) shape_value = np.array([], dtype=np.int64) @@ -349,7 +349,7 @@ class SerializeSparseTest(test.TestCase): serialize_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: sp_input0 = self._SparseTensorPlaceholder() sp_input1 = self._SparseTensorPlaceholder() input0_val = self._SparseTensorValue_5x6(np.arange(6)) @@ -384,7 +384,7 @@ class SerializeSparseTest(test.TestCase): serialize_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: sp_input0 = self._SparseTensorPlaceholder() sp_input1 = self._SparseTensorPlaceholder() input0_val = self._SparseTensorValue_5x6(np.arange(6)) @@ -419,7 +419,7 @@ class SerializeSparseTest(test.TestCase): serialize_fn, deserialize_fn, out_type=dtypes.string): - with self.test_session(use_gpu=False) as sess: + with self.cached_session(use_gpu=False) as sess: sp_input0 = self._SparseTensorPlaceholder() input0_val = self._SparseTensorValue_5x6(np.arange(6)) serialized0 = serialize_fn(sp_input0, out_type=out_type) diff --git a/tensorflow/python/kernel_tests/sparse_slice_op_test.py b/tensorflow/python/kernel_tests/sparse_slice_op_test.py index 97f30daf4a..098353741f 100644 --- a/tensorflow/python/kernel_tests/sparse_slice_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_slice_op_test.py @@ -80,7 +80,7 @@ class SparseSliceOpTest(test.TestCase): self._SparseTensorValue_3x4x2()) def testSliceMatrixRows(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_4x6() sp_tensor0 = sparse_ops.sparse_slice(sp_input, [0, 0], [2, 6]) sp_tensor1 = sparse_ops.sparse_slice(sp_input, [2, 0], [3, 7]) @@ -97,7 +97,7 @@ class SparseSliceOpTest(test.TestCase): self.assertAllEqual(sp_tensor1.dense_shape.eval(), [2, 6]) def testSliceMatrixUnevenCols(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_5x7() sp_tensor0 = sparse_ops.sparse_slice(sp_input, [0, 0], [5, 3]) sp_tensor1 = sparse_ops.sparse_slice(sp_input, [0, 3], [5, 2]) @@ -138,7 +138,7 @@ class SparseSliceOpTest(test.TestCase): self.assertAllEqual(sp_tensor3.dense_shape.eval(), [5, 1]) def testSliceMatrixUnevenRows(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_5x7() sp_tensor0 = sparse_ops.sparse_slice(sp_input, [0, 0], [3, 7]) sp_tensor1 = sparse_ops.sparse_slice(sp_input, [3, 0], [3, 7]) @@ -174,7 +174,7 @@ class SparseSliceOpTest(test.TestCase): return def testSliceAllRows(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_4x6() sp_tensor0 = sparse_ops.sparse_slice(sp_input, [0, 0], [1, 6]) sp_tensor1 = sparse_ops.sparse_slice(sp_input, [1, 0], [1, 6]) @@ -196,7 +196,7 @@ class SparseSliceOpTest(test.TestCase): self.assertAllEqual(sp_tensor3.dense_shape.eval(), [1, 6]) def testSliceColumns(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_4x6() sparse_tensor0 = sparse_ops.sparse_slice(sp_input, [0, 0], [4, 2]) sparse_tensor1 = sparse_ops.sparse_slice(sp_input, [0, 2], [5, 2]) @@ -216,7 +216,7 @@ class SparseSliceOpTest(test.TestCase): self.assertAllEqual(sparse_tensor2.dense_shape.eval(), [4, 2]) def testSliceAllColumns(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_input = self._SparseTensor_4x6() sparse_tensor0 = sparse_ops.sparse_slice(sp_input, [0, 0], [4, 1]) sparse_tensor1 = sparse_ops.sparse_slice(sp_input, [0, 1], [4, 1]) @@ -252,7 +252,7 @@ class SparseSliceOpTest(test.TestCase): ([0, 2], [5, 2]), ([0, 4], [5, 3])] - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for start, size in start_and_size: sp_output = sparse_ops.sparse_slice(sp_input, start, size) nnz_in = len(sp_input.values.eval()) diff --git a/tensorflow/python/kernel_tests/sparse_split_op_test.py b/tensorflow/python/kernel_tests/sparse_split_op_test.py index 23c6c390b2..95661ded4b 100644 --- a/tensorflow/python/kernel_tests/sparse_split_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_split_op_test.py @@ -76,7 +76,7 @@ class SparseSplitOpTest(test.TestCase): )) def testSplitMatrixRows(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_tensors = sparse_ops.sparse_split( sp_input=self._SparseTensor_4x6(), num_split=2, axis=0) self.assertAllEqual(len(sp_tensors), 2) @@ -93,7 +93,7 @@ class SparseSplitOpTest(test.TestCase): self.assertAllEqual(sp_tensors[1].dense_shape.eval(), [2, 6]) def testSplitMatrixUnevenCols(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_tensors_3 = sparse_ops.sparse_split( sp_input=self._SparseTensor_5x7(), num_split=3, axis=1) self.assertAllEqual(len(sp_tensors_3), 3) @@ -132,7 +132,7 @@ class SparseSplitOpTest(test.TestCase): self.assertAllEqual(sp_tensors_4[3].dense_shape.eval(), [5, 1]) def testSplitMatrixUnevenRows(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_tensors_2 = sparse_ops.sparse_split( sp_input=self._SparseTensor_5x7(), num_split=2, axis=0) self.assertAllEqual(sp_tensors_2[0].indices.eval(), @@ -168,7 +168,7 @@ class SparseSplitOpTest(test.TestCase): return def testSplitAllRows(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sp_tensors = sparse_ops.sparse_split( sp_input=self._SparseTensor_4x6(), num_split=4, axis=0) self.assertAllEqual(len(sp_tensors), 4) @@ -190,7 +190,7 @@ class SparseSplitOpTest(test.TestCase): self.assertAllEqual(sp_tensors[3].dense_shape.eval(), [1, 6]) def testSplitColumns(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sparse_tensors = sparse_ops.sparse_split( sp_input=self._SparseTensor_4x6(), num_split=3, axis=1) self.assertAllEqual(len(sparse_tensors), 3) @@ -208,7 +208,7 @@ class SparseSplitOpTest(test.TestCase): self.assertAllEqual(sparse_tensors[2].dense_shape.eval(), [4, 2]) def testSplitAllColumns(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): sparse_tensors = sparse_ops.sparse_split( sp_input=self._SparseTensor_4x6(), num_split=6, axis=1) self.assertAllEqual(len(sparse_tensors), 6) @@ -237,7 +237,7 @@ class SparseSplitOpTest(test.TestCase): def testSliceConcat(self): for sp_input in (self._SparseTensorValue_3x4x2(), self._SparseTensor_3x4x2()): - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): sparse_tensors = sparse_ops.sparse_split( sp_input=sp_input, num_split=2, axis=1) concat_tensor = sparse_ops.sparse_concat(1, sparse_tensors) diff --git a/tensorflow/python/kernel_tests/sparse_tensor_dense_matmul_grad_test.py b/tensorflow/python/kernel_tests/sparse_tensor_dense_matmul_grad_test.py index e8b94294b1..b8f33d6a81 100644 --- a/tensorflow/python/kernel_tests/sparse_tensor_dense_matmul_grad_test.py +++ b/tensorflow/python/kernel_tests/sparse_tensor_dense_matmul_grad_test.py @@ -72,7 +72,7 @@ class SparseTensorDenseMatMulGradientTest(test.TestCase): matmul = sparse_ops.sparse_tensor_dense_matmul( sp_t, dense_t, adjoint_a=adjoint_a, adjoint_b=adjoint_b, name=name) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): dense_t_shape = [m, k] if adjoint_b else [k, m] sp_t_val_shape = [nnz] err = gradient_checker.compute_gradient_error( diff --git a/tensorflow/python/kernel_tests/sparse_tensor_dense_matmul_op_test.py b/tensorflow/python/kernel_tests/sparse_tensor_dense_matmul_op_test.py index e20c699252..fe334045af 100644 --- a/tensorflow/python/kernel_tests/sparse_tensor_dense_matmul_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_tensor_dense_matmul_op_test.py @@ -65,7 +65,7 @@ class SparseTensorDenseMatMulTest(test.TestCase): x_values = x[np.where(x)] x_shape = x.shape - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): sp_x_value = sparse_tensor.SparseTensorValue( indices=x_indices, values=x_values, dense_shape=x_shape) tf_value_ans = sparse_ops.sparse_tensor_dense_matmul( @@ -133,7 +133,7 @@ class SparseTensorDenseMatMulTest(test.TestCase): def testInvalidIndicesForSparseTensorDenseMatmul(self): # Note: use_gpu=False because nice errors are only returned from CPU kernel. - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = np.matrix([[1, 10]]).astype(np.int64) values = np.array([10]).astype(np.float32) shape = [3, 2] @@ -166,7 +166,7 @@ class SparseTensorDenseMatMulTest(test.TestCase): # Note: use_gpu=False because nice errors are only returned from CPU kerne if not test.is_gpu_available(): return - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): indices = np.array([[1, 10]]).astype(np.int64) values = np.array([10]).astype(np.float32) shape = [3, 2] diff --git a/tensorflow/python/kernel_tests/sparse_tensors_map_ops_test.py b/tensorflow/python/kernel_tests/sparse_tensors_map_ops_test.py index fdfe1001b8..e08464a701 100644 --- a/tensorflow/python/kernel_tests/sparse_tensors_map_ops_test.py +++ b/tensorflow/python/kernel_tests/sparse_tensors_map_ops_test.py @@ -99,7 +99,7 @@ class SparseTensorsMapTest(test.TestCase): self.assertAllEqual(combined_shape, [2, 5, 6]) def testFeedAddTakeMany(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input0_val = self._SparseTensorValue_5x6(np.arange(6)) input1_val = self._SparseTensorValue_3x4(np.arange(6)) @@ -125,7 +125,7 @@ class SparseTensorsMapTest(test.TestCase): self.assertAllEqual(combined_shape, [2, 5, 6]) def testAddManyTakeManyRoundTrip(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: # N == 4 because shape_value == [4, 5] indices_value = np.array([[0, 0], [0, 1], [2, 0]], dtype=np.int64) values_value = np.array([b"a", b"b", b"c"]) @@ -147,7 +147,7 @@ class SparseTensorsMapTest(test.TestCase): self.assertAllEqual(roundtrip_value.dense_shape, shape_value) def testDeserializeFailsInconsistentRank(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: sp_input = self._SparseTensorPlaceholder() input0_val = self._SparseTensorValue_5x6(np.arange(6)) input1_val = self._SparseTensorValue_1x1x1() @@ -168,7 +168,7 @@ class SparseTensorsMapTest(test.TestCase): sess.run(sp_roundtrip) def testTakeManyFailsWrongInputOp(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: input_val = self._SparseTensorValue_5x6(np.arange(6)) handle = add_sparse_to_tensors_map(input_val) handle_value = sess.run(handle) diff --git a/tensorflow/python/kernel_tests/sparse_to_dense_op_py_test.py b/tensorflow/python/kernel_tests/sparse_to_dense_op_py_test.py index c71746cc99..7f63532e10 100644 --- a/tensorflow/python/kernel_tests/sparse_to_dense_op_py_test.py +++ b/tensorflow/python/kernel_tests/sparse_to_dense_op_py_test.py @@ -42,38 +42,38 @@ def _SparseToDense(sparse_indices, class SparseToDenseTest(test.TestCase): def testInt(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): tf_ans = _SparseToDense([1, 3], [5], 1, 0).eval() np_ans = np.array([0, 1, 0, 1, 0]).astype(np.int32) self.assertAllClose(np_ans, tf_ans) def testFloat(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): tf_ans = _SparseToDense([1, 3], [5], 1.0, 0.0).eval() np_ans = np.array([0, 1, 0, 1, 0]).astype(np.float32) self.assertAllClose(np_ans, tf_ans) def testString(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): tf_ans = _SparseToDense([1, 3], [5], "a", "b").eval() np_ans = np.array(["b", "a", "b", "a", "b"]).astype(np.string_) self.assertAllEqual(np_ans, tf_ans) def testSetValue(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): tf_ans = _SparseToDense([1, 3], [5], [1, 2], -1).eval() np_ans = np.array([-1, 1, -1, 2, -1]).astype(np.int32) self.assertAllClose(np_ans, tf_ans) def testSetSingleValue(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): tf_ans = _SparseToDense([1, 3], [5], 1, -1).eval() np_ans = np.array([-1, 1, -1, 1, -1]).astype(np.int32) self.assertAllClose(np_ans, tf_ans) def test2d(self): # pylint: disable=bad-whitespace - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): tf_ans = _SparseToDense([[1, 3], [2, 0]], [3, 4], 1, -1).eval() np_ans = np.array([[-1, -1, -1, -1], [-1, -1, -1, 1], @@ -86,7 +86,7 @@ class SparseToDenseTest(test.TestCase): self.assertAllEqual(x, [0, 0, 7, 0]) def test3d(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): tf_ans = _SparseToDense([[1, 3, 0], [2, 0, 1]], [3, 4, 2], 1, -1).eval() np_ans = np.ones((3, 4, 2), dtype=np.int32) * -1 np_ans[1, 3, 0] = 1 @@ -176,7 +176,7 @@ class SparseToDenseTest(test.TestCase): dense_without_validation.eval() def testShapeInferenceKnownShape(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = array_ops.placeholder(dtypes.int64) shape = [4, 5, 6] @@ -188,7 +188,7 @@ class SparseToDenseTest(test.TestCase): self.assertEqual(output.get_shape().as_list(), [None, None, None]) def testShapeInferenceUnknownShape(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): indices = array_ops.placeholder(dtypes.int64) shape = array_ops.placeholder(dtypes.int64) output = sparse_ops.sparse_to_dense(indices, shape, 1, 0) diff --git a/tensorflow/python/kernel_tests/sparse_xent_op_test.py b/tensorflow/python/kernel_tests/sparse_xent_op_test.py index a841fe83a7..0510bc5321 100644 --- a/tensorflow/python/kernel_tests/sparse_xent_op_test.py +++ b/tensorflow/python/kernel_tests/sparse_xent_op_test.py @@ -63,7 +63,7 @@ class SparseXentTest(test.TestCase): def _testXent(self, np_features, np_labels): np_loss, np_backprop = self._npXent(np_features, np_labels) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: loss, backprop = gen_nn_ops.sparse_softmax_cross_entropy_with_logits( np_features, np_labels) tf_loss, tf_backprop = sess.run([loss, backprop]) @@ -72,7 +72,7 @@ class SparseXentTest(test.TestCase): def testSingleClass(self): for label_dtype in np.int32, np.int64: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: loss, backprop = gen_nn_ops.sparse_softmax_cross_entropy_with_logits( np.array([[1.], [-1.], [0.]]).astype(np.float32), np.array([0, 0, 0]).astype(label_dtype)) @@ -86,7 +86,7 @@ class SparseXentTest(test.TestCase): labels = [4, 3, 0, -1] if test.is_built_with_cuda() and test.is_gpu_available(): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: loss, backprop = ( gen_nn_ops.sparse_softmax_cross_entropy_with_logits( features, labels)) @@ -100,7 +100,7 @@ class SparseXentTest(test.TestCase): self.assertAllClose( [np.nan, 1.3862, 3.4420, np.nan], tf_loss, rtol=1e-3, atol=1e-3) - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: loss, backprop = ( gen_nn_ops.sparse_softmax_cross_entropy_with_logits(features, labels)) with self.assertRaisesOpError("Received a label value of"): @@ -141,19 +141,19 @@ class SparseXentTest(test.TestCase): np.array([1.3862, 3.4420]), np_loss, rtol=1.e-3, atol=1.e-3) def testShapeMismatch(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesRegexp(ValueError, ".*Rank mismatch:*"): nn_ops.sparse_softmax_cross_entropy_with_logits( labels=[[0, 2]], logits=[[0., 1.], [2., 3.], [2., 3.]]) def testScalar(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaisesRegexp(ValueError, ".*Logits cannot be scalars*"): nn_ops.sparse_softmax_cross_entropy_with_logits( labels=constant_op.constant(0), logits=constant_op.constant(1.0)) def testLabelsPlaceholderScalar(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): labels = array_ops.placeholder(np.int32) y = nn_ops.sparse_softmax_cross_entropy_with_logits( labels=labels, logits=[[7.]]) @@ -161,7 +161,7 @@ class SparseXentTest(test.TestCase): y.eval(feed_dict={labels: 0}) def testVector(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): loss = nn_ops.sparse_softmax_cross_entropy_with_logits( labels=constant_op.constant(0), logits=constant_op.constant([1.0])) self.assertAllClose(0.0, loss.eval()) @@ -188,7 +188,7 @@ class SparseXentTest(test.TestCase): self._testXent(np.zeros((0, 3)), np.zeros((0,), dtype=np.int32)) def testGradient(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): l = constant_op.constant([3, 0, 1], name="l") f = constant_op.constant( [0.1, 0.2, 0.3, 0.4, 0.1, 0.4, 0.9, 1.6, 0.1, 0.8, 2.7, 6.4], @@ -222,7 +222,7 @@ class SparseXentTest(test.TestCase): np_loss, np_backprop = self._npXent(np.array(features), np.array(labels)) # manually reshape loss np_loss = np.reshape(np_loss, np.array(labels).shape) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: loss = nn_ops.sparse_softmax_cross_entropy_with_logits( labels=labels, logits=features) backprop = loss.op.inputs[0].op.outputs[1] @@ -242,7 +242,7 @@ class SparseXentTest(test.TestCase): self._testHighDim(features, labels) def testScalarHandling(self): - with self.test_session(use_gpu=False) as sess: + with self.session(use_gpu=False) as sess: with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, ".*labels must be 1-D.*"): labels = array_ops.placeholder(dtypes.int32, shape=[None, 1]) diff --git a/tensorflow/python/kernel_tests/split_op_test.py b/tensorflow/python/kernel_tests/split_op_test.py index 3f9b029a6a..944b0e59b1 100644 --- a/tensorflow/python/kernel_tests/split_op_test.py +++ b/tensorflow/python/kernel_tests/split_op_test.py @@ -54,13 +54,13 @@ class SplitOpTest(test.TestCase): model_input = array_ops.placeholder(dtypes.float32) inp = np.zeros((1, 10)) # check that we still fail at runtime if the shapes were unknown - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: with self.assertRaises(errors_impl.InvalidArgumentError): sess.run(array_ops.split(model_input, [4]), {model_input: inp}) # test that we can pass a scalar Tensor as num_splits for axis in [0, -2]: - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: result = sess.run( array_ops.split( array_ops.ones([4, 4]), @@ -82,7 +82,7 @@ class SplitOpTest(test.TestCase): model_input2 = array_ops.placeholder(dtypes.float32, shape=[None, 2]) result = array_ops.split(model_input2, [2, 2], axis=0)[0] - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: sess.run(result, feed_dict={model_input2: np.ones([4, 2])}) def testFailWithoutExplicitNum(self): @@ -90,7 +90,7 @@ class SplitOpTest(test.TestCase): value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: with self.assertRaises(ValueError) as context: sess.run(array_ops.split(value, size_splits), {size_splits: [2, 2, 6]}) self.assertTrue("Cannot infer num from shape" in str(context.exception)) @@ -211,7 +211,7 @@ class SplitOpTest(test.TestCase): def testOutputShape(self): for axis in [1, -1]: - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tensor = array_ops.placeholder(dtypes.float32, shape=[None, 12]) size_splits = [3, 7, 2] outputs = array_ops.split(tensor, size_splits, axis) @@ -312,7 +312,7 @@ class SplitOpTest(test.TestCase): def _testGradientsSimple(self, dtype): inp = self._makeData((4, 4), dtype) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inp_tensor = ops.convert_to_tensor(inp) s = array_ops.split(value=inp_tensor, num_or_size_splits=4, axis=1) inp_grads = [self._makeData((4, 1), dtype)for _ in range(4)] @@ -375,7 +375,7 @@ class SplitOpTest(test.TestCase): splits = array_ops.placeholder(dtypes.int32, [3]) y = array_ops.split(values, splits, axis=x) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, "must have exactly one element"): sess.run(y, {x: np.array([], dtype=np.int32), splits: [4, 11, 15]}) diff --git a/tensorflow/python/kernel_tests/stack_op_test.py b/tensorflow/python/kernel_tests/stack_op_test.py index 2a33c594a4..4b355620bf 100644 --- a/tensorflow/python/kernel_tests/stack_op_test.py +++ b/tensorflow/python/kernel_tests/stack_op_test.py @@ -43,7 +43,7 @@ class StackOpTest(test.TestCase): def testSimple(self): np.random.seed(7) - with self.test_session(use_gpu=True): + with self.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]: data = np.random.randn(*shape).astype(dtype) @@ -56,7 +56,7 @@ class StackOpTest(test.TestCase): def testSimpleParallelCPU(self): np.random.seed(7) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): data = np.random.randn(*shape).astype(np.float32) xs = list(map(constant_op.constant, data)) @@ -65,7 +65,7 @@ class StackOpTest(test.TestCase): def testSimpleParallelGPU(self): np.random.seed(7) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): data = np.random.randn(*shape).astype(np.float32) xs = list(map(constant_op.constant, data)) @@ -74,7 +74,7 @@ class StackOpTest(test.TestCase): def testConst(self): np.random.seed(7) - with self.test_session(use_gpu=True): + with self.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]: data = np.random.randn(*shape).astype(dtype) @@ -98,7 +98,7 @@ class StackOpTest(test.TestCase): def testConstParallelCPU(self): np.random.seed(7) - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): data = np.random.randn(*shape).astype(np.float32) if len(shape) == 1: @@ -112,7 +112,7 @@ class StackOpTest(test.TestCase): def testConstParallelGPU(self): np.random.seed(7) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): data = np.random.randn(*shape).astype(np.float32) if len(shape) == 1: @@ -129,7 +129,7 @@ class StackOpTest(test.TestCase): for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): data = np.random.randn(*shape) shapes = [shape[1:]] * shape[0] - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # TODO(irving): Remove list() once we handle maps correctly xs = list(map(constant_op.constant, data)) c = array_ops.stack(xs) @@ -143,7 +143,7 @@ class StackOpTest(test.TestCase): shapes = [shape[1:]] * shape[0] out_shape = list(shape[1:]) out_shape.insert(1, shape[0]) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): # TODO(irving): Remove list() once we handle maps correctly xs = list(map(constant_op.constant, data)) c = array_ops.stack(xs, axis=1) @@ -152,7 +152,7 @@ class StackOpTest(test.TestCase): def testZeroSizeCPU(self): # Verify that stack doesn't crash for zero size inputs - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): for shape in (0,), (3, 0), (0, 3): x = np.zeros((2,) + shape).astype(np.int32) p = array_ops.stack(list(x)).eval() @@ -163,7 +163,7 @@ class StackOpTest(test.TestCase): def testZeroSizeGPU(self): # Verify that stack doesn't crash for zero size inputs - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in (0,), (3, 0), (0, 3): x = np.zeros((2,) + shape).astype(np.int32) p = array_ops.stack(list(x)).eval() @@ -173,7 +173,7 @@ class StackOpTest(test.TestCase): self.assertAllEqual(p, x) def testAxis0DefaultCPU(self): - with self.test_session(use_gpu=False): + with self.session(use_gpu=False): t = [constant_op.constant([1, 2, 3]), constant_op.constant([4, 5, 6])] stacked = array_ops.stack(t).eval() parallel_stacked = array_ops.parallel_stack(t).eval() @@ -183,7 +183,7 @@ class StackOpTest(test.TestCase): self.assertAllEqual(parallel_stacked, expected) def testAxis0DefaultGPU(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): t = [constant_op.constant([1, 2, 3]), constant_op.constant([4, 5, 6])] stacked = array_ops.stack(t).eval() parallel_stacked = array_ops.parallel_stack(t).eval() @@ -201,7 +201,7 @@ class StackOpTest(test.TestCase): for j in range(-i, i): test_arrays = np_split_squeeze(expected, j) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): actual_pack = array_ops.stack(test_arrays, axis=j) self.assertEqual(expected.shape, actual_pack.get_shape()) actual_pack = actual_pack.eval() @@ -226,7 +226,7 @@ class StackOpTest(test.TestCase): class AutomaticStackingTest(test.TestCase): def testSimple(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): self.assertAllEqual( [1, 0, 2], ops.convert_to_tensor([1, constant_op.constant(0), 2]).eval()) @@ -246,7 +246,7 @@ class AutomaticStackingTest(test.TestCase): ]).eval()) def testWithNDArray(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): result = ops.convert_to_tensor([[[0., 0.], constant_op.constant([1., 1.])], np.array( @@ -256,7 +256,7 @@ class AutomaticStackingTest(test.TestCase): result.eval()) def testVariable(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): v = variables.Variable(17) result = ops.convert_to_tensor([[0, 0, 0], [0, v, 0], [0, 0, 0]]) v.initializer.run() @@ -307,7 +307,7 @@ class AutomaticStackingTest(test.TestCase): self.assertEqual(dtypes.float64, t_2.dtype) def testPlaceholder(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): # Test using placeholder with a defined shape. ph_0 = array_ops.placeholder(dtypes.int32, shape=[]) result_0 = ops.convert_to_tensor([[0, 0, 0], [0, ph_0, 0], [0, 0, 0]]) @@ -333,7 +333,7 @@ class AutomaticStackingTest(test.TestCase): # Dynamic shape error. ph_1 = array_ops.placeholder(dtypes.int32) result_1 = ops.convert_to_tensor([[0, 0, 0], [0, ph_1, 0], [0, 0, 0]]) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(errors_impl.InvalidArgumentError): result_1.eval(feed_dict={ph_1: [1]}) diff --git a/tensorflow/python/kernel_tests/stack_ops_test.py b/tensorflow/python/kernel_tests/stack_ops_test.py index afd2eaffab..1aa12009ea 100644 --- a/tensorflow/python/kernel_tests/stack_ops_test.py +++ b/tensorflow/python/kernel_tests/stack_ops_test.py @@ -33,7 +33,7 @@ from tensorflow.python.platform import test class StackOpTest(test.TestCase): def _testStackPushPop(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): h = gen_data_flow_ops.stack_v2( -1, elem_type=dtypes.float32, stack_name="foo") c = gen_data_flow_ops.stack_push_v2(h, [[4.0, 5.0]]) @@ -46,7 +46,7 @@ class StackOpTest(test.TestCase): self._testStackPushPop(use_gpu=True) def _testStackPushPopSwap(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): a = np.arange(2000) x = constant_op.constant(a, dtype=dtypes.float32) h = gen_data_flow_ops.stack_v2( @@ -61,7 +61,7 @@ class StackOpTest(test.TestCase): self._testStackPushPopSwap(use_gpu=True) def _testStackWhileSwap(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): n = constant_op.constant(0) h = gen_data_flow_ops.stack_v2( -1, elem_type=dtypes.float32, stack_name="foo") @@ -98,7 +98,7 @@ class StackOpTest(test.TestCase): self._testStackWhileSwap(use_gpu=True) def _testMultiStack(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): h1 = gen_data_flow_ops.stack_v2( -1, elem_type=dtypes.float32, stack_name="foo") c1 = gen_data_flow_ops.stack_push_v2(h1, 4.0) @@ -118,7 +118,7 @@ class StackOpTest(test.TestCase): def _testSameNameStacks(self, use_gpu): """Different stacks with the same name do not interfere.""" - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: h1 = gen_data_flow_ops.stack_v2( -1, elem_type=dtypes.float32, stack_name="foo") h2 = gen_data_flow_ops.stack_v2( @@ -140,7 +140,7 @@ class StackOpTest(test.TestCase): self._testSameNameStacks(use_gpu=True) def _testCloseStack(self, use_gpu): - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: h = gen_data_flow_ops.stack_v2( -1, elem_type=dtypes.float32, stack_name="foo") c1 = gen_data_flow_ops.stack_close_v2(h) @@ -151,7 +151,7 @@ class StackOpTest(test.TestCase): self._testCloseStack(use_gpu=True) def _testPushCloseStack(self, use_gpu): - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: h = gen_data_flow_ops.stack_v2( -1, elem_type=dtypes.float32, stack_name="foo") c = gen_data_flow_ops.stack_push_v2(h, [[4.0, 5.0]]) @@ -168,7 +168,7 @@ class StackOpRefTest(test.TestCase): """Tests for deprecated non-resource variant of stack ops.""" def _testStackPushPop(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): h = gen_data_flow_ops._stack(dtypes.float32, stack_name="foo") c = gen_data_flow_ops.stack_push(h, [[4.0, 5.0]]) with ops.control_dependencies([c]): @@ -180,7 +180,7 @@ class StackOpRefTest(test.TestCase): self._testStackPushPop(use_gpu=True) def _testStackPushPopSwap(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): a = np.arange(2000) x = constant_op.constant(a, dtype=dtypes.float32) h = gen_data_flow_ops._stack(dtypes.float32, stack_name="foo") @@ -194,7 +194,7 @@ class StackOpRefTest(test.TestCase): self._testStackPushPopSwap(use_gpu=True) def _testMultiStack(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): h1 = gen_data_flow_ops._stack(dtypes.float32, stack_name="foo") c1 = gen_data_flow_ops.stack_push(h1, 4.0) with ops.control_dependencies([c1]): @@ -207,7 +207,7 @@ class StackOpRefTest(test.TestCase): self.assertAllClose(9.0, r.eval()) def _testStackWhileSwap(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): n = constant_op.constant(0) h = gen_data_flow_ops._stack(dtypes.float32, stack_name="foo") @@ -247,7 +247,7 @@ class StackOpRefTest(test.TestCase): self._testMultiStack(use_gpu=True) def _testSameNameStacks(self, use_gpu): - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): h1 = gen_data_flow_ops._stack(dtypes.float32, stack_name="foo") c1 = gen_data_flow_ops.stack_push(h1, 4.0) h2 = gen_data_flow_ops._stack(dtypes.float32, stack_name="foo") @@ -260,7 +260,7 @@ class StackOpRefTest(test.TestCase): self._testSameNameStacks(use_gpu=True) def _testCloseStack(self, use_gpu): - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: h = gen_data_flow_ops._stack(dtypes.float32, stack_name="foo") c1 = gen_data_flow_ops.stack_close(h) sess.run(c1) @@ -270,7 +270,7 @@ class StackOpRefTest(test.TestCase): self._testCloseStack(use_gpu=True) def _testPushCloseStack(self, use_gpu): - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: h = gen_data_flow_ops._stack(dtypes.float32, stack_name="foo") c = gen_data_flow_ops.stack_push(h, [[4.0, 5.0]]) with ops.control_dependencies([c]): diff --git a/tensorflow/python/kernel_tests/stage_op_test.py b/tensorflow/python/kernel_tests/stage_op_test.py index dd06d30391..b814843b86 100644 --- a/tensorflow/python/kernel_tests/stage_op_test.py +++ b/tensorflow/python/kernel_tests/stage_op_test.py @@ -41,7 +41,7 @@ class StageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: sess.run(stage, feed_dict={x: -1}) for i in range(10): _, yval = sess.run([stage, y], feed_dict={x: i}) @@ -60,7 +60,7 @@ class StageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: sess.run(stage, feed_dict={x: -1}) for i in range(10): _, yval = sess.run([stage, y], feed_dict={x: i}) @@ -85,7 +85,7 @@ class StageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: sess.run(stage, feed_dict={x: -1}) for i in range(10): _, yval = sess.run([stage, y], feed_dict={x: i}) @@ -126,7 +126,7 @@ class StageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: for i in range(10): sess.run(stage, feed_dict={x: i}) @@ -150,7 +150,7 @@ class StageTest(test.TestCase): G.finalize() - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: sess.run(stage, feed_dict={x: -1}) self.assertEqual(sess.run(size), 1) sess.run(stage, feed_dict={x: -1}) @@ -181,7 +181,7 @@ class StageTest(test.TestCase): queue = Queue.Queue() n = 8 - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # Stage data in a separate thread which will block # when it hits the staging area's capacity and thus # not fill the queue with n tokens @@ -245,7 +245,7 @@ class StageTest(test.TestCase): queue = Queue.Queue() n = 8 - with self.test_session(use_gpu=True, graph=G) as sess: + with self.session(use_gpu=True, graph=G) as sess: # Stage data in a separate thread which will block # when it hits the staging area's capacity and thus # not fill the queue with n tokens diff --git a/tensorflow/python/kernel_tests/string_length_op_test.py b/tensorflow/python/kernel_tests/string_length_op_test.py index 4afe3ad3f4..57db7302b1 100644 --- a/tensorflow/python/kernel_tests/string_length_op_test.py +++ b/tensorflow/python/kernel_tests/string_length_op_test.py @@ -38,7 +38,7 @@ class StringLengthOpTest(test.TestCase): expected_utf8_byte_lengths = [6, 4] expected_utf8_char_lengths = [5, 1] - with self.test_session() as sess: + with self.session() as sess: utf8_byte_lengths = string_ops.string_length(utf8_strings, unit="BYTE") utf8_char_lengths = string_ops.string_length( utf8_strings, unit="UTF8_CHAR") @@ -56,7 +56,7 @@ class StringLengthOpTest(test.TestCase): # argument for the 'name' parameter. Check that we don't break such code. strings = [[["1", "12"], ["123", "1234"], ["12345", "123456"]]] lengths = string_ops.string_length(strings, "some_name") - with self.test_session(): + with self.session(): self.assertAllEqual(lengths.eval(), [[[1, 2], [3, 4], [5, 6]]]) diff --git a/tensorflow/python/kernel_tests/svd_op_test.py b/tensorflow/python/kernel_tests/svd_op_test.py index d20567bf0e..57298c0fec 100644 --- a/tensorflow/python/kernel_tests/svd_op_test.py +++ b/tensorflow/python/kernel_tests/svd_op_test.py @@ -50,7 +50,7 @@ class SvdOpTest(test.TestCase): linalg_ops.svd(vector) def testConcurrentExecutesWithoutError(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: all_ops = [] for compute_uv_ in True, False: for full_matrices_ in True, False: @@ -140,7 +140,7 @@ def _GetSvdOpTest(dtype_, shape_, use_static_shape_, compute_uv_, low=-1.0, high=1.0, size=np.prod(shape_)).reshape(shape_).astype(dtype_) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: if use_static_shape_: x_tf = constant_op.constant(x_np) else: @@ -229,7 +229,7 @@ def _GetSvdGradOpTest(dtype_, shape_, compute_uv_, full_matrices_): tol = 3e-2 else: tol = 1e-6 - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tf_a = constant_op.constant(a) if compute_uv_: tf_s, tf_u, tf_v = _NormalizingSvd(tf_a) diff --git a/tensorflow/python/kernel_tests/tensor_array_ops_test.py b/tensorflow/python/kernel_tests/tensor_array_ops_test.py index 0ad2063558..91bd93712a 100644 --- a/tensorflow/python/kernel_tests/tensor_array_ops_test.py +++ b/tensorflow/python/kernel_tests/tensor_array_ops_test.py @@ -77,7 +77,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testTensorArrayWriteRead(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -98,7 +98,7 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual(-3.0, d2) def _testTensorArrayWritePack(self, tf_dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=tf_dtype, tensor_array_name="foo", size=3) @@ -129,7 +129,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testEmptyTensorArrayPack(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", size=3) @@ -144,7 +144,7 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual([3, 0, 1], c0.shape) def _testTensorArrayWriteConcat(self, tf_dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=tf_dtype, tensor_array_name="foo", size=3, infer_shape=False) @@ -172,7 +172,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArrayWriteConcat(dtypes.string) def _testTensorArrayReadOrPackNotAllValuesAvailableFillsZeros(self): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -205,7 +205,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArrayReadOrPackNotAllValuesAvailableInferShapeFillsZeros() def _testTensorArrayUnpackRead(self, tf_dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): convert = _make_converter(tf_dtype) ta = _make_ta(3, "foo", dtype=tf_dtype) @@ -256,7 +256,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArrayUnpackReadMaybeLegacy() def _testTensorArraySplitRead(self, tf_dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): convert = _make_converter(tf_dtype) # Split an empty vector @@ -308,7 +308,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArraySplitRead(dtypes.string) def testTensorGradArrayWriteRead(self): - with self.test_session(use_gpu=True) as session: + with self.session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -341,7 +341,7 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual(-2.0, g_d2) def testTensorGradArrayDynamicWriteRead(self): - with self.test_session(use_gpu=True) as session: + with self.session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -382,7 +382,7 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual(3, g_vs) def testTensorGradAccessTwiceReceiveSameObject(self): - with self.test_session(use_gpu=True) as session: + with self.session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", size=3) g_ta_0 = ta.grad("grad") @@ -399,7 +399,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testTensorArrayWriteWrongIndexOrDataTypeFails(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = _make_ta(3, "foo", dtype=dtypes.float32) # Test writing the wrong datatype with self.assertRaisesOpError( @@ -418,7 +418,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testTensorArrayReadWrongIndexOrDataTypeFails(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = _make_ta(3, "foo", dtype=dtypes.float32) w0 = ta.write(0, [[4.0, 5.0]]) @@ -441,7 +441,7 @@ class TensorArrayTest(test.TestCase): self.evaluate(ta.read(3)) def testTensorArrayWriteMultipleFails(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", size=3) @@ -452,7 +452,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testTensorArrayConcatIncompatibleShapesFails(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -484,7 +484,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testTensorArraySplitIncompatibleShapesFails(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): in_eager_mode = context.executing_eagerly() ta = _make_ta(3, "foo") with self.assertRaisesOpError( @@ -513,7 +513,7 @@ class TensorArrayTest(test.TestCase): self.evaluate(ta.split([1.0], [1]).flow) def _testTensorArrayWriteGradientAddMultipleAdds(self, dtype): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtype, tensor_array_name="foo", size=3, infer_shape=False) ta_grad = ta.grad("grad") @@ -552,7 +552,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArrayWriteGradientAddMultipleAdds(dtype) def testTensorArrayGradWithShapeKnownElementShape(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: ta = tensor_array_ops.TensorArray( size=3, dtype=dtypes.float32, @@ -581,7 +581,7 @@ class TensorArrayTest(test.TestCase): sess.run(read_value, feed_dict={value: fed_value})) def testTensorArrayGradWithShapeUnknownElementShape(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: ta = tensor_array_ops.TensorArray( size=3, dtype=dtypes.float32, element_shape=None) # Note that element_shape is unknown @@ -605,7 +605,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testMultiTensorArray(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): h1 = tensor_array_ops.TensorArray( size=1, dtype=dtypes.float32, tensor_array_name="foo") w1 = h1.write(0, 4.0) @@ -621,7 +621,7 @@ class TensorArrayTest(test.TestCase): self.assertAllClose(9.0, val) def _testTensorArrayGradientWriteReadType(self, dtype): - with self.test_session(use_gpu=True) as session: + with self.cached_session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.as_dtype(dtype), tensor_array_name="foo", @@ -672,7 +672,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArrayGradientWriteReadType(dtype) def _testTensorArrayGradientWritePackConcatAndRead(self): - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -708,7 +708,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testTensorArrayReadTwice(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): value = constant_op.constant([[1.0, -1.0], [10.0, -10.0]]) ta_readonce = tensor_array_ops.TensorArray( @@ -736,7 +736,7 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual([1.0, -1.0], self.evaluate(r1_readtwice)) def _testTensorArrayGradientUnpackRead(self): - with self.test_session(use_gpu=True) as session: + with self.cached_session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -764,7 +764,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArrayGradientUnpackRead() def testTensorArrayGradientSplitConcat(self): - with self.test_session(use_gpu=True) as session: + with self.session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", size=2, infer_shape=False) @@ -787,7 +787,7 @@ class TensorArrayTest(test.TestCase): grad_vals[0]) def _testTensorArrayGradientDynamicUnpackRead(self): - with self.test_session(use_gpu=True) as session: + with self.cached_session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -813,14 +813,14 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testCloseTensorArray(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", size=3) self.evaluate(ta.close()) @test_util.run_in_graph_and_eager_modes def testSizeTensorArray(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", size=3) s = ta.size() @@ -828,7 +828,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testWriteCloseTensorArray(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -840,7 +840,7 @@ class TensorArrayTest(test.TestCase): def _testWhileLoopWritePackGradients(self, dynamic_size, dtype): np_dtype = dtype.as_numpy_dtype - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): def func(v0, state0, var): ta = tensor_array_ops.TensorArray( dtype=dtype, @@ -938,7 +938,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testGradSerialTwoLoops(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): def loop(x): num_steps = 100 acc = tensor_array_ops.TensorArray( @@ -977,7 +977,7 @@ class TensorArrayTest(test.TestCase): self.assertAllClose(31.0, self.evaluate(grad)) def testSumOfTwoReadVariablesWithoutRepeatGrad(self): - with self.test_session(use_gpu=True) as session: + with self.session(use_gpu=True) as session: a = array_ops.identity( np.arange( 3 * 5, dtype=np.float32).reshape(3, 5) + 1) @@ -1050,7 +1050,7 @@ class TensorArrayTest(test.TestCase): self._grad_source_for_name("foo/gradients/bar/gradients_0/baz")) def testWriteShape(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", size=3) c0 = constant_op.constant([4.0, 5.0]) @@ -1074,7 +1074,7 @@ class TensorArrayTest(test.TestCase): w0.write(0, c2) def testPartlyUnknownShape(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", size=6) @@ -1115,7 +1115,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def _testUnpackShape(self): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -1149,7 +1149,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testSplitShape(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -1179,7 +1179,7 @@ class TensorArrayTest(test.TestCase): ta1.handle.op.get_attr("element_shape")).ndims, None) def testWriteUnknownShape(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -1191,7 +1191,7 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual(r0.get_shape(), tensor_shape.unknown_shape()) def _testGradientWhenNotAllComponentsRead(self): - with self.test_session(use_gpu=True) as session: + with self.cached_session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray(dtype=dtypes.float32, size=2) x = constant_op.constant([2.0, 3.0]) w = ta.unstack(x) @@ -1205,7 +1205,7 @@ class TensorArrayTest(test.TestCase): self._testGradientWhenNotAllComponentsRead() def _testTensorArrayUnpackDynamic(self): - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, size=3, dynamic_size=True) x = constant_op.constant([1.0, 2.0, 3.0]) @@ -1220,7 +1220,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArrayUnpackDynamic() def testTensorArraySplitDynamic(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, size=3, dynamic_size=True) x = constant_op.constant([1.0, 2.0, 3.0]) @@ -1232,7 +1232,7 @@ class TensorArrayTest(test.TestCase): self.assertAllEqual(np.array([1.0, 1.0, 1.0]), sess.run(grad)[0]) def _testTensorArrayEvalEmpty(self): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, size=0, dynamic_size=False, infer_shape=False) with self.assertRaisesOpError( @@ -1247,7 +1247,7 @@ class TensorArrayTest(test.TestCase): # this test is ill-defined for Eager mode --- unpacking an empty tensor # gives an empty list / there is not equivalent of "mark_used" in Eager def _testTensorArrayEvalEmptyWithDefault(self): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, size=0, dynamic_size=False, infer_shape=True) self.assertEqual(0, ta.size().eval()) @@ -1264,7 +1264,7 @@ class TensorArrayTest(test.TestCase): self._testTensorArrayEvalEmptyWithDefault() def testTensorArrayScatterReadAndGradients(self): - with self.test_session(use_gpu=True) as session: + with self.session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -1291,7 +1291,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testTensorArrayWriteGatherAndGradients(self): - with self.test_session(use_gpu=True) as session: + with self.session(use_gpu=True) as session: ta = tensor_array_ops.TensorArray( dtype=dtypes.float32, tensor_array_name="foo", @@ -1435,7 +1435,7 @@ class TensorArrayTest(test.TestCase): @test_util.run_in_graph_and_eager_modes def testTensorArrayIdentity(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): ta0 = tensor_array_ops.TensorArray(dtype=dtypes.float32, size=2, infer_shape=False) ta1 = tensor_array_ops.TensorArray(dtype=dtypes.int32, size=4, @@ -1500,14 +1500,14 @@ class TensorArrayTest(test.TestCase): # dy is outside of the gradients name scope; tf.gradients must # wrap it in the correct name scope. dx, = gradients_impl.gradients(ys=[y], xs=[x], grad_ys=[dy]) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: vdx, vdy = sess.run([dx, dy]) self.assertAllClose(vdx, vdy) def testTensorArrayInt64GPU(self): if not test.is_gpu_available(): return - with self.test_session(use_gpu=True, force_gpu=True) as sess: + with self.session(use_gpu=True, force_gpu=True) as sess: value = array_ops.placeholder(dtypes.int64) ta = tensor_array_ops.TensorArray(dtype=dtypes.int64, size=2) ta = ta.scatter([0, 1], value) diff --git a/tensorflow/python/kernel_tests/tensordot_op_test.py b/tensorflow/python/kernel_tests/tensordot_op_test.py index d8d76440f1..123c9b376c 100644 --- a/tensorflow/python/kernel_tests/tensordot_op_test.py +++ b/tensorflow/python/kernel_tests/tensordot_op_test.py @@ -165,7 +165,7 @@ def _get_tensordot_tests(dtype_, rank_a_, rank_b_, num_dims_, dynamic_shape_): for _ in range(num_trials): a_np, b_np, a_dims_np, b_dims_np = _generate_random_tensors_and_dims() np_ans = np.tensordot(a_np, b_np, axes=(a_dims_np, b_dims_np)) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: if dynamic_shape_: a = array_ops.placeholder(dtype_) b = array_ops.placeholder(dtype_) @@ -201,7 +201,7 @@ def _get_tensordot_tests(dtype_, rank_a_, rank_b_, num_dims_, dynamic_shape_): all_axes.append(a_np.ndim - 1) for axes in all_axes: np_ans = np.tensordot(a_np, b_np, axes=axes) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: if dynamic_shape_: a = array_ops.placeholder(dtype_) b = array_ops.placeholder(dtype_) diff --git a/tensorflow/python/kernel_tests/topk_op_test.py b/tensorflow/python/kernel_tests/topk_op_test.py index d5f0726106..d9f340de6b 100644 --- a/tensorflow/python/kernel_tests/topk_op_test.py +++ b/tensorflow/python/kernel_tests/topk_op_test.py @@ -46,7 +46,7 @@ class TopKTest(test.TestCase): sorted=True): # pylint: disable=redefined-builtin np_expected_values = np.array(expected_values) np_expected_indices = np.array(expected_indices) - with self.test_session(use_gpu=True) as sess: + with self.cached_session(use_gpu=True) as sess: values_op, indices_op = nn_ops.top_k(inputs, k, sorted=sorted) values, indices = sess.run([values_op, indices_op]) @@ -183,7 +183,7 @@ class TopKTest(test.TestCase): def testKNegative(self): inputs = [[0.1, 0.2], [0.3, 0.4]] - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): k = array_ops.placeholder(dtypes.int32) values, _ = nn_ops.top_k(inputs, k) with self.assertRaisesOpError("Need k >= 0, got -7"): @@ -196,7 +196,7 @@ class TopKTest(test.TestCase): nn_ops.top_k(inputs, 4) def testTopKGradients(self): - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: inputs = array_ops.placeholder(dtypes.float32, shape=[2, 5]) values, _ = nn_ops.top_k(inputs, 3) grad = sess.run( diff --git a/tensorflow/python/kernel_tests/trace_op_test.py b/tensorflow/python/kernel_tests/trace_op_test.py index a5d5bcc149..f1abaefb66 100644 --- a/tensorflow/python/kernel_tests/trace_op_test.py +++ b/tensorflow/python/kernel_tests/trace_op_test.py @@ -30,7 +30,7 @@ class TraceTest(test.TestCase): def compare(self, x): np_ans = np.trace(x, axis1=-2, axis2=-1) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): tf_ans = math_ops.trace(x).eval() self.assertAllClose(tf_ans, np_ans) diff --git a/tensorflow/python/kernel_tests/transpose_op_test.py b/tensorflow/python/kernel_tests/transpose_op_test.py index a825052dd2..8c11c20709 100644 --- a/tensorflow/python/kernel_tests/transpose_op_test.py +++ b/tensorflow/python/kernel_tests/transpose_op_test.py @@ -47,7 +47,7 @@ class TransposeTest(test.TestCase): np_ans = self._np_transpose(x, perm) if conjugate: np_ans = np.conj(np_ans) - with self.test_session(use_gpu=False): + with self.cached_session(use_gpu=False): inx = ops.convert_to_tensor(x) y = array_ops.transpose(inx, p, conjugate=conjugate) tf_ans = y.eval() @@ -78,7 +78,7 @@ class TransposeTest(test.TestCase): np_ans = self._np_transpose(x, perm) if conjugate: np_ans = np.conj(np_ans) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(x) y = array_ops.transpose(inx, p, conjugate=conjugate) tf_ans = y.eval() @@ -165,7 +165,7 @@ class TransposeTest(test.TestCase): total_size = np.prod(input_shape) inp = np.arange(1, total_size + 1, dtype=datatype).reshape(input_shape) np_ans = self._np_transpose(inp, perm) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(inp) y = array_ops.transpose(inx, perm) tf_ans = y.eval() @@ -186,7 +186,7 @@ class TransposeTest(test.TestCase): total_size = np.prod(input_shape) inp = np.arange(1, total_size + 1, dtype=np.float32).reshape(input_shape) np_ans = self._np_transpose(inp, perm) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(inp) y = array_ops.transpose(inx, perm) tf_ans = y.eval() @@ -221,7 +221,7 @@ class TransposeTest(test.TestCase): total_size = np.prod(input_shape) inp = np.arange(1, total_size + 1, dtype=np.float32).reshape(input_shape) np_ans = self._np_transpose(inp, perm) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(inp) y = array_ops.transpose(inx, perm) tf_ans = y.eval() @@ -243,7 +243,7 @@ class TransposeTest(test.TestCase): total_size = np.prod(input_shape) inp = np.arange(1, total_size + 1, dtype=datatype).reshape(input_shape) np_ans = self._np_transpose(inp, perm) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(inp) y = array_ops.transpose(inx, perm) tf_ans = y.eval() @@ -264,7 +264,7 @@ class TransposeTest(test.TestCase): total_size = np.prod(input_shape) inp = np.arange(1, total_size + 1, dtype=np.float32).reshape(input_shape) np_ans = self._np_transpose(inp, perm) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(inp) y = array_ops.transpose(inx, perm) tf_ans = y.eval() @@ -316,7 +316,7 @@ class TransposeTest(test.TestCase): # generate input data with random ints from 0 to 9. inp = np.random.randint(10, size=input_shape) np_ans = self._np_transpose(inp, perm) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(inp) y = array_ops.transpose(inx, perm) tf_ans = y.eval() @@ -337,7 +337,7 @@ class TransposeTest(test.TestCase): x = np.arange(0, 8).reshape([2, 4]).astype(np.float32) p = np.array([1, 0]).astype(perm_dtype) np_ans = np.copy(x).transpose(p) - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): inx = ops.convert_to_tensor(x) inp = constant_op.constant(p) y = array_ops.transpose(inx, inp) @@ -414,7 +414,7 @@ class TransposeTest(test.TestCase): def testTranspose2DAuto(self): x_np = [[1, 2, 3], [4, 5, 6]] for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu): + with self.cached_session(use_gpu=use_gpu): x_tf = array_ops.transpose(x_np).eval() self.assertAllEqual(x_tf, [[1, 4], [2, 5], [3, 6]]) diff --git a/tensorflow/python/kernel_tests/unstack_op_test.py b/tensorflow/python/kernel_tests/unstack_op_test.py index b373c419b6..48ab258b7f 100644 --- a/tensorflow/python/kernel_tests/unstack_op_test.py +++ b/tensorflow/python/kernel_tests/unstack_op_test.py @@ -41,7 +41,7 @@ class UnstackOpTest(test.TestCase): def testSimple(self): np.random.seed(7) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): for dtype in [ np.bool, np.float16, np.float32, np.float64, np.int32, np.int64 @@ -60,7 +60,7 @@ class UnstackOpTest(test.TestCase): if not test_util.is_gpu_available(): self.skipTest('No GPU available') np.random.seed(7) - with self.test_session(use_gpu=True, force_gpu=True): + with self.session(use_gpu=True, force_gpu=True): for shape in (2,), (3,), (2, 3), (3, 2), (4, 3, 2): for dtype in [np.float16, np.float32, np.float64, np.int32, np.int64]: data = np.random.randn(*shape).astype(dtype) @@ -78,7 +78,7 @@ class UnstackOpTest(test.TestCase): data = np.random.randn(*shape) shapes = [shape[1:]] * shape[0] for i in xrange(shape[0]): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = constant_op.constant(data) cs = array_ops.unstack(x, num=shape[0]) err = gradient_checker.compute_gradient_error(x, shape, cs[i], @@ -91,7 +91,7 @@ class UnstackOpTest(test.TestCase): out_shape = list(shape) del out_shape[1] for i in xrange(shape[1]): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): x = constant_op.constant(data) cs = array_ops.unstack(x, num=shape[1], axis=1) err = gradient_checker.compute_gradient_error(x, shape, cs[i], diff --git a/tensorflow/python/kernel_tests/variables_test.py b/tensorflow/python/kernel_tests/variables_test.py index 70507ad6a6..b3eebf8316 100644 --- a/tensorflow/python/kernel_tests/variables_test.py +++ b/tensorflow/python/kernel_tests/variables_test.py @@ -124,7 +124,7 @@ class VariablesTestCase(test.TestCase): self.assertAllClose(4.0, var.eval()) def testResourceAssignments(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): var = resource_variable_ops.ResourceVariable(0.0) plus_one = var.assign_add(1.0) minus_one = var.assign_sub(2.0) diff --git a/tensorflow/python/kernel_tests/where_op_test.py b/tensorflow/python/kernel_tests/where_op_test.py index 04ac589432..fca45c3ece 100644 --- a/tensorflow/python/kernel_tests/where_op_test.py +++ b/tensorflow/python/kernel_tests/where_op_test.py @@ -37,7 +37,7 @@ from tensorflow.python.platform import test class WhereOpTest(test.TestCase): def _testWhere(self, x, truth, expected_err_re=None): - with self.test_session(use_gpu=True): + with self.cached_session(use_gpu=True): ans = array_ops.where(x) self.assertEqual([None, x.ndim], ans.get_shape().as_list()) if expected_err_re is None: @@ -48,7 +48,7 @@ class WhereOpTest(test.TestCase): ans.eval() def testWrongNumbers(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): with self.assertRaises(ValueError): array_ops.where([False, True], [1, 2], None) with self.assertRaises(ValueError): @@ -132,7 +132,7 @@ class WhereOpTest(test.TestCase): def testThreeArgument(self): x = np.array([[-2, 3, -1], [1, -3, -3]]) np_val = np.where(x > 0, x * x, -x) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tf_val = array_ops.where(constant_op.constant(x) > 0, x * x, -x).eval() self.assertAllEqual(tf_val, np_val) @@ -141,7 +141,7 @@ class WhereOpTest(test.TestCase): c_mat = np.array([[False] * 192, [True] * 192] * 8192) # [16384, 192] c_vec = np.array([False, True] * 8192) # [16384] np_val = np.where(c_mat, x * x, -x) - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): tf_val = array_ops.where(c_vec, x * x, -x).eval() self.assertAllEqual(tf_val, np_val) diff --git a/tensorflow/python/kernel_tests/xent_op_test.py b/tensorflow/python/kernel_tests/xent_op_test.py index 729885169e..c3c7f867a1 100644 --- a/tensorflow/python/kernel_tests/xent_op_test.py +++ b/tensorflow/python/kernel_tests/xent_op_test.py @@ -53,7 +53,7 @@ class XentTest(test.TestCase): def _testXent(self, np_features, np_labels, use_gpu=False): np_loss, np_backprop = self._npXent(np_features, np_labels) - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: loss, backprop = gen_nn_ops.softmax_cross_entropy_with_logits( np_features, np_labels) tf_loss, tf_backprop = sess.run([loss, backprop]) @@ -62,7 +62,7 @@ class XentTest(test.TestCase): def _testXentWrapper(self, np_features, np_labels, dim=-1, use_gpu=False): np_loss, _ = self._npXent(np_features, np_labels, dim=dim) - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: loss = nn_ops.softmax_cross_entropy_with_logits( labels=np_labels, logits=np_features, dim=dim) tf_loss = sess.run(loss) @@ -76,7 +76,7 @@ class XentTest(test.TestCase): def _testSingleClass(self, use_gpu=False): for dtype in np.float16, np.float32: - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: loss, backprop = gen_nn_ops.softmax_cross_entropy_with_logits( np.array([[1.], [-1.], [0.]]).astype(dtype), np.array([[-1.], [0.], [1.]]).astype(dtype)) @@ -145,7 +145,7 @@ class XentTest(test.TestCase): tf_l = constant_op.constant( np.array([[0., 0., 0., 1.], [0., .5, .5, 0.]]).astype(np.float32)) for use_gpu in [False, True]: - with self.test_session(use_gpu=use_gpu) as sess: + with self.cached_session(use_gpu=use_gpu) as sess: loss, backprop = gen_nn_ops.softmax_cross_entropy_with_logits( tf_f, tf_l) tf_loss, tf_backprop = sess.run([loss, backprop]) @@ -277,7 +277,7 @@ class XentTest(test.TestCase): features = np.zeros([0, 2, 4]).astype(np.float32) labels = np.zeros([0, 2, 4]).astype(np.float32) np_loss, _ = self._npXent(features, labels) - with self.test_session(use_gpu=True) as sess: + with self.session(use_gpu=True) as sess: loss = nn_ops.softmax_cross_entropy_with_logits( labels=labels, logits=features) tf_loss = sess.run(loss) diff --git a/tensorflow/python/kernel_tests/zero_division_test.py b/tensorflow/python/kernel_tests/zero_division_test.py index dd0214e0f1..e68b96e670 100644 --- a/tensorflow/python/kernel_tests/zero_division_test.py +++ b/tensorflow/python/kernel_tests/zero_division_test.py @@ -27,7 +27,7 @@ from tensorflow.python.platform import test class ZeroDivisionTest(test.TestCase): def testZeros(self): - with self.test_session(use_gpu=True): + with self.session(use_gpu=True): for dtype in dtypes.uint8, dtypes.int16, dtypes.int32, dtypes.int64: zero = constant_op.constant(0, dtype=dtype) one = constant_op.constant(1, dtype=dtype) -- GitLab From 6f7cbd60a2a5ecadd9f96ed267d758834adda159 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Tue, 16 Oct 2018 18:11:46 -0700 Subject: [PATCH 0185/1825] Makes a number of Keras layer tests run in both graph and eager modes. PiperOrigin-RevId: 217422423 --- .../keras/layers/advanced_activations_test.py | 64 ++- .../python/keras/layers/embeddings_test.py | 16 +- tensorflow/python/keras/layers/lstm_test.py | 423 +++++++++--------- tensorflow/python/keras/layers/merge_test.py | 72 ++- tensorflow/python/keras/layers/noise_test.py | 20 +- .../python/keras/layers/normalization_test.py | 196 ++++---- .../python/keras/layers/serialization_test.py | 2 + .../python/keras/layers/simplernn_test.py | 92 ++-- 8 files changed, 437 insertions(+), 448 deletions(-) diff --git a/tensorflow/python/keras/layers/advanced_activations_test.py b/tensorflow/python/keras/layers/advanced_activations_test.py index c41087be0a..4aadf535e0 100644 --- a/tensorflow/python/keras/layers/advanced_activations_test.py +++ b/tensorflow/python/keras/layers/advanced_activations_test.py @@ -19,55 +19,52 @@ from __future__ import division from __future__ import print_function from tensorflow.python import keras +from tensorflow.python.eager import context +from tensorflow.python.framework import test_util as tf_test_util from tensorflow.python.keras import testing_utils from tensorflow.python.platform import test +@tf_test_util.run_all_in_graph_and_eager_modes class AdvancedActivationsTest(test.TestCase): def test_leaky_relu(self): - with self.cached_session(): - for alpha in [0., .5, -1.]: - testing_utils.layer_test(keras.layers.LeakyReLU, - kwargs={'alpha': alpha}, - input_shape=(2, 3, 4)) + for alpha in [0., .5, -1.]: + testing_utils.layer_test(keras.layers.LeakyReLU, + kwargs={'alpha': alpha}, + input_shape=(2, 3, 4)) def test_prelu(self): - with self.cached_session(): - testing_utils.layer_test(keras.layers.PReLU, kwargs={}, - input_shape=(2, 3, 4)) + testing_utils.layer_test(keras.layers.PReLU, kwargs={}, + input_shape=(2, 3, 4)) def test_prelu_share(self): - with self.cached_session(): - testing_utils.layer_test(keras.layers.PReLU, - kwargs={'shared_axes': 1}, - input_shape=(2, 3, 4)) + testing_utils.layer_test(keras.layers.PReLU, + kwargs={'shared_axes': 1}, + input_shape=(2, 3, 4)) def test_elu(self): - with self.cached_session(): - for alpha in [0., .5, -1.]: - testing_utils.layer_test(keras.layers.ELU, - kwargs={'alpha': alpha}, - input_shape=(2, 3, 4)) + for alpha in [0., .5, -1.]: + testing_utils.layer_test(keras.layers.ELU, + kwargs={'alpha': alpha}, + input_shape=(2, 3, 4)) def test_thresholded_relu(self): - with self.cached_session(): - testing_utils.layer_test(keras.layers.ThresholdedReLU, - kwargs={'theta': 0.5}, - input_shape=(2, 3, 4)) + testing_utils.layer_test(keras.layers.ThresholdedReLU, + kwargs={'theta': 0.5}, + input_shape=(2, 3, 4)) def test_softmax(self): - with self.cached_session(): - testing_utils.layer_test(keras.layers.Softmax, - kwargs={'axis': 1}, - input_shape=(2, 3, 4)) + testing_utils.layer_test(keras.layers.Softmax, + kwargs={'axis': 1}, + input_shape=(2, 3, 4)) def test_relu(self): - with self.cached_session(): - testing_utils.layer_test(keras.layers.ReLU, - kwargs={'max_value': 10}, - input_shape=(2, 3, 4)) - x = keras.backend.ones((3, 4)) + testing_utils.layer_test(keras.layers.ReLU, + kwargs={'max_value': 10}, + input_shape=(2, 3, 4)) + x = keras.backend.ones((3, 4)) + if not context.executing_eagerly(): # Test that we use `leaky_relu` when appropriate in graph mode. self.assertTrue( 'LeakyRelu' in keras.layers.ReLU(negative_slope=0.2)(x).name) @@ -79,10 +76,9 @@ class AdvancedActivationsTest(test.TestCase): def test_relu_with_invalid_arg(self): with self.assertRaisesRegexp( ValueError, 'max_value of Relu layer cannot be negative value: -10'): - with self.cached_session(): - testing_utils.layer_test(keras.layers.ReLU, - kwargs={'max_value': -10}, - input_shape=(2, 3, 4)) + testing_utils.layer_test(keras.layers.ReLU, + kwargs={'max_value': -10}, + input_shape=(2, 3, 4)) with self.assertRaisesRegexp( ValueError, 'negative_slope of Relu layer cannot be negative value: -2'): diff --git a/tensorflow/python/keras/layers/embeddings_test.py b/tensorflow/python/keras/layers/embeddings_test.py index 2e42e403aa..aaa17b7e96 100644 --- a/tensorflow/python/keras/layers/embeddings_test.py +++ b/tensorflow/python/keras/layers/embeddings_test.py @@ -69,16 +69,16 @@ class EmbeddingTest(test.TestCase): input_dtype='int32', expected_output_dtype='float32') + @tf_test_util.run_in_graph_and_eager_modes() def test_embedding_correctness(self): - with self.cached_session(): - layer = keras.layers.Embedding(output_dim=2, input_dim=2) - layer.build((None, 2)) - matrix = np.array([[1, 1], [2, 2]]) - layer.set_weights([matrix]) + layer = keras.layers.Embedding(output_dim=2, input_dim=2) + layer.build((None, 2)) + matrix = np.array([[1, 1], [2, 2]]) + layer.set_weights([matrix]) - inputs = keras.backend.constant([[0, 1, 0]], dtype='int32') - outputs = keras.backend.eval(layer(inputs)) - self.assertAllClose(outputs, [[[1, 1], [2, 2], [1, 1]]]) + inputs = keras.backend.constant([[0, 1, 0]], dtype='int32') + outputs = keras.backend.eval(layer(inputs)) + self.assertAllClose(outputs, [[[1, 1], [2, 2], [1, 1]]]) @tf_test_util.run_in_graph_and_eager_modes() def test_eager_gpu_cpu(self): diff --git a/tensorflow/python/keras/layers/lstm_test.py b/tensorflow/python/keras/layers/lstm_test.py index f536915324..e0094d99f4 100644 --- a/tensorflow/python/keras/layers/lstm_test.py +++ b/tensorflow/python/keras/layers/lstm_test.py @@ -24,12 +24,14 @@ from tensorflow.python import keras from tensorflow.python.framework import test_util as tf_test_util from tensorflow.python.keras import testing_utils from tensorflow.python.platform import test +from tensorflow.python.training import adam +from tensorflow.python.training import gradient_descent from tensorflow.python.training.rmsprop import RMSPropOptimizer +@tf_test_util.run_all_in_graph_and_eager_modes class LSTMLayerTest(test.TestCase): - @tf_test_util.run_in_graph_and_eager_modes def test_return_sequences_LSTM(self): num_samples = 2 timesteps = 3 @@ -56,7 +58,6 @@ class LSTMLayerTest(test.TestCase): outputs = model.layers[-1].output self.assertEquals(outputs.get_shape().as_list(), [None, timesteps, units]) - @tf_test_util.run_in_graph_and_eager_modes def test_dynamic_behavior_LSTM(self): num_samples = 2 timesteps = 3 @@ -70,7 +71,6 @@ class LSTMLayerTest(test.TestCase): y = np.random.random((num_samples, units)) model.train_on_batch(x, y) - @tf_test_util.run_in_graph_and_eager_modes def test_dropout_LSTM(self): num_samples = 2 timesteps = 3 @@ -83,7 +83,6 @@ class LSTMLayerTest(test.TestCase): 'recurrent_dropout': 0.1}, input_shape=(num_samples, timesteps, embedding_dim)) - @tf_test_util.run_in_graph_and_eager_modes def test_implementation_mode_LSTM(self): num_samples = 2 timesteps = 3 @@ -96,120 +95,36 @@ class LSTMLayerTest(test.TestCase): 'implementation': mode}, input_shape=(num_samples, timesteps, embedding_dim)) - def test_statefulness_LSTM(self): - num_samples = 2 - timesteps = 3 - embedding_dim = 4 - units = 2 - layer_class = keras.layers.LSTM - with self.cached_session(): - 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='sgd', 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) - - def test_regularizers_LSTM(self): - embedding_dim = 4 - layer_class = keras.layers.LSTM - with self.cached_session(): - 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) - self.assertEqual(len(layer.get_losses_for(x)), 1) - def test_constraints_LSTM(self): embedding_dim = 4 layer_class = keras.layers.LSTM - with self.cached_session(): - 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) - - @tf_test_util.run_in_graph_and_eager_modes + 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_with_masking_layer_LSTM(self): layer_class = keras.layers.LSTM - with self.cached_session(): - inputs = np.random.random((2, 3, 4)) - targets = np.abs(np.random.random((2, 3, 5))) - targets /= targets.sum(axis=-1, keepdims=True) - model = keras.models.Sequential() - model.add(keras.layers.Masking(input_shape=(3, 4))) - model.add(layer_class(units=5, return_sequences=True, unroll=False)) - model.compile(loss='categorical_crossentropy', - optimizer=RMSPropOptimizer(0.01)) - model.fit(inputs, targets, epochs=1, batch_size=2, verbose=1) + inputs = np.random.random((2, 3, 4)) + targets = np.abs(np.random.random((2, 3, 5))) + targets /= targets.sum(axis=-1, keepdims=True) + model = keras.models.Sequential() + model.add(keras.layers.Masking(input_shape=(3, 4))) + model.add(layer_class(units=5, return_sequences=True, unroll=False)) + model.compile(loss='categorical_crossentropy', + optimizer=RMSPropOptimizer(0.01)) + model.fit(inputs, targets, epochs=1, batch_size=2, verbose=1) def test_from_config_LSTM(self): layer_class = keras.layers.LSTM @@ -225,25 +140,25 @@ class LSTMLayerTest(test.TestCase): units = 3 num_samples = 2 - with self.cached_session(): - # Test with Keras tensor - inputs = keras.Input((timesteps, embedding_dim)) - initial_state = [keras.Input((units,)) for _ in range(num_states)] - layer = keras.layers.LSTM(units) - if len(initial_state) == 1: - output = layer(inputs, initial_state=initial_state[0]) - else: - output = layer(inputs, initial_state=initial_state) - assert initial_state[0] in layer._inbound_nodes[0].input_tensors - - model = keras.models.Model([inputs] + initial_state, output) - model.compile(loss='categorical_crossentropy', optimizer='adam') - - inputs = np.random.random((num_samples, timesteps, embedding_dim)) - initial_state = [np.random.random((num_samples, units)) - for _ in range(num_states)] - targets = np.random.random((num_samples, units)) - model.train_on_batch([inputs] + initial_state, targets) + # Test with Keras tensor + inputs = keras.Input((timesteps, embedding_dim)) + initial_state = [keras.Input((units,)) for _ in range(num_states)] + layer = keras.layers.LSTM(units) + if len(initial_state) == 1: + output = layer(inputs, initial_state=initial_state[0]) + else: + output = layer(inputs, initial_state=initial_state) + assert initial_state[0] in layer._inbound_nodes[0].input_tensors + + model = keras.models.Model([inputs] + initial_state, output) + model.compile(loss='categorical_crossentropy', + optimizer=adam.AdamOptimizer()) + + inputs = np.random.random((num_samples, timesteps, embedding_dim)) + initial_state = [np.random.random((num_samples, units)) + for _ in range(num_states)] + targets = np.random.random((num_samples, units)) + model.train_on_batch([inputs] + initial_state, targets) def test_specify_initial_state_non_keras_tensor(self): num_states = 2 @@ -252,21 +167,21 @@ class LSTMLayerTest(test.TestCase): units = 3 num_samples = 2 - with self.cached_session(): - # Test with non-Keras tensor - inputs = keras.Input((timesteps, embedding_dim)) - initial_state = [keras.backend.random_normal_variable( - (num_samples, units), 0, 1) - for _ in range(num_states)] - layer = keras.layers.LSTM(units) - output = layer(inputs, initial_state=initial_state) + # Test with non-Keras tensor + inputs = keras.Input((timesteps, embedding_dim)) + initial_state = [keras.backend.random_normal_variable( + (num_samples, units), 0, 1) + for _ in range(num_states)] + layer = keras.layers.LSTM(units) + output = layer(inputs, initial_state=initial_state) - model = keras.models.Model(inputs, output) - model.compile(loss='categorical_crossentropy', optimizer='adam') + model = keras.models.Model(inputs, output) + model.compile(loss='categorical_crossentropy', + optimizer=adam.AdamOptimizer()) - inputs = np.random.random((num_samples, timesteps, embedding_dim)) - targets = np.random.random((num_samples, units)) - model.train_on_batch(inputs, targets) + inputs = np.random.random((num_samples, timesteps, embedding_dim)) + targets = np.random.random((num_samples, units)) + model.train_on_batch(inputs, targets) def test_reset_states_with_values(self): num_states = 2 @@ -275,29 +190,28 @@ class LSTMLayerTest(test.TestCase): units = 3 num_samples = 2 - with self.cached_session(): - layer = keras.layers.LSTM(units, stateful=True) - layer.build((num_samples, timesteps, embedding_dim)) - layer.reset_states() - assert len(layer.states) == num_states - assert layer.states[0] is not None - self.assertAllClose( - keras.backend.eval(layer.states[0]), - np.zeros(keras.backend.int_shape(layer.states[0])), - atol=1e-4) - state_shapes = [keras.backend.int_shape(state) for state in layer.states] - values = [np.ones(shape) for shape in state_shapes] - if len(values) == 1: - values = values[0] - layer.reset_states(values) - self.assertAllClose( - keras.backend.eval(layer.states[0]), - np.ones(keras.backend.int_shape(layer.states[0])), - atol=1e-4) - - # Test with invalid data - with self.assertRaises(ValueError): - layer.reset_states([1] * (len(layer.states) + 1)) + layer = keras.layers.LSTM(units, stateful=True) + layer.build((num_samples, timesteps, embedding_dim)) + layer.reset_states() + assert len(layer.states) == num_states + assert layer.states[0] is not None + self.assertAllClose( + keras.backend.eval(layer.states[0]), + np.zeros(keras.backend.int_shape(layer.states[0])), + atol=1e-4) + state_shapes = [keras.backend.int_shape(state) for state in layer.states] + values = [np.ones(shape) for shape in state_shapes] + if len(values) == 1: + values = values[0] + layer.reset_states(values) + self.assertAllClose( + keras.backend.eval(layer.states[0]), + np.ones(keras.backend.int_shape(layer.states[0])), + atol=1e-4) + + # Test with invalid data + with self.assertRaises(ValueError): + layer.reset_states([1] * (len(layer.states) + 1)) def test_specify_state_with_masking(self): num_states = 2 @@ -306,21 +220,20 @@ class LSTMLayerTest(test.TestCase): units = 3 num_samples = 2 - with self.cached_session(): - inputs = keras.Input((timesteps, embedding_dim)) - _ = keras.layers.Masking()(inputs) - initial_state = [keras.Input((units,)) for _ in range(num_states)] - output = keras.layers.LSTM(units)(inputs, initial_state=initial_state) + inputs = keras.Input((timesteps, embedding_dim)) + _ = keras.layers.Masking()(inputs) + initial_state = [keras.Input((units,)) for _ in range(num_states)] + output = keras.layers.LSTM(units)(inputs, initial_state=initial_state) - model = keras.models.Model([inputs] + initial_state, output) - model.compile(loss='categorical_crossentropy', - optimizer=RMSPropOptimizer(0.01)) + model = keras.models.Model([inputs] + initial_state, output) + model.compile(loss='categorical_crossentropy', + optimizer=RMSPropOptimizer(0.01)) - inputs = np.random.random((num_samples, timesteps, embedding_dim)) - initial_state = [np.random.random((num_samples, units)) - for _ in range(num_states)] - targets = np.random.random((num_samples, units)) - model.train_on_batch([inputs] + initial_state, targets) + inputs = np.random.random((num_samples, timesteps, embedding_dim)) + initial_state = [np.random.random((num_samples, units)) + for _ in range(num_states)] + targets = np.random.random((num_samples, units)) + model.train_on_batch([inputs] + initial_state, targets) def test_return_state(self): num_states = 2 @@ -329,17 +242,16 @@ class LSTMLayerTest(test.TestCase): units = 3 num_samples = 2 - with self.cached_session(): - inputs = keras.Input(batch_shape=(num_samples, timesteps, embedding_dim)) - layer = keras.layers.LSTM(units, return_state=True, stateful=True) - outputs = layer(inputs) - state = outputs[1:] - assert len(state) == num_states - model = keras.models.Model(inputs, state[0]) + inputs = keras.Input(batch_shape=(num_samples, timesteps, embedding_dim)) + layer = keras.layers.LSTM(units, return_state=True, stateful=True) + outputs = layer(inputs) + state = outputs[1:] + assert len(state) == num_states + model = keras.models.Model(inputs, state[0]) - inputs = np.random.random((num_samples, timesteps, embedding_dim)) - state = model.predict(inputs) - self.assertAllClose(keras.backend.eval(layer.states[0]), state, atol=1e-4) + inputs = np.random.random((num_samples, timesteps, embedding_dim)) + state = model.predict(inputs) + self.assertAllClose(keras.backend.eval(layer.states[0]), state, atol=1e-4) def test_state_reuse(self): timesteps = 3 @@ -347,16 +259,15 @@ class LSTMLayerTest(test.TestCase): units = 3 num_samples = 2 - with self.cached_session(): - inputs = keras.Input(batch_shape=(num_samples, timesteps, embedding_dim)) - layer = keras.layers.LSTM(units, return_state=True, return_sequences=True) - outputs = layer(inputs) - output, state = outputs[0], outputs[1:] - output = keras.layers.LSTM(units)(output, initial_state=state) - model = keras.models.Model(inputs, output) + inputs = keras.Input(batch_shape=(num_samples, timesteps, embedding_dim)) + layer = keras.layers.LSTM(units, return_state=True, return_sequences=True) + outputs = layer(inputs) + output, state = outputs[0], outputs[1:] + output = keras.layers.LSTM(units)(output, initial_state=state) + model = keras.models.Model(inputs, output) - inputs = np.random.random((num_samples, timesteps, embedding_dim)) - outputs = model.predict(inputs) + inputs = np.random.random((num_samples, timesteps, embedding_dim)) + outputs = model.predict(inputs) def test_initial_states_as_other_inputs(self): timesteps = 3 @@ -366,25 +277,109 @@ class LSTMLayerTest(test.TestCase): num_states = 2 layer_class = keras.layers.LSTM + # Test with Keras tensor + main_inputs = keras.Input((timesteps, embedding_dim)) + initial_state = [keras.Input((units,)) for _ in range(num_states)] + inputs = [main_inputs] + initial_state + + layer = layer_class(units) + output = layer(inputs) + assert initial_state[0] in layer._inbound_nodes[0].input_tensors + + model = keras.models.Model(inputs, output) + model.compile(loss='categorical_crossentropy', + optimizer=adam.AdamOptimizer()) + + main_inputs = np.random.random((num_samples, timesteps, embedding_dim)) + initial_state = [np.random.random((num_samples, units)) + for _ in range(num_states)] + targets = np.random.random((num_samples, units)) + model.train_on_batch([main_inputs] + initial_state, targets) + + +class LSTMLayerGraphOnlyTest(test.TestCase): + + def test_statefulness_LSTM(self): + num_samples = 2 + timesteps = 3 + embedding_dim = 4 + units = 2 + layer_class = keras.layers.LSTM with self.cached_session(): - # Test with Keras tensor - main_inputs = keras.Input((timesteps, embedding_dim)) - initial_state = [keras.Input((units,)) for _ in range(num_states)] - inputs = [main_inputs] + initial_state + 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()) - layer = layer_class(units) - output = layer(inputs) - assert initial_state[0] in layer._inbound_nodes[0].input_tensors + # 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) - model = keras.models.Model(inputs, output) - model.compile(loss='categorical_crossentropy', optimizer='adam') + # check that the call to `predict` updated the states + out5 = model.predict(np.ones((num_samples, timesteps))) + self.assertNotEqual(out4.max(), out5.max()) - main_inputs = np.random.random((num_samples, timesteps, embedding_dim)) - initial_state = [np.random.random((num_samples, units)) - for _ in range(num_states)] - targets = np.random.random((num_samples, units)) - model.train_on_batch([main_inputs] + initial_state, targets) + # 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) + + def test_regularizers_LSTM(self): + embedding_dim = 4 + layer_class = keras.layers.LSTM + with self.cached_session(): + 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) + self.assertEqual(len(layer.get_losses_for(x)), 1) if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/layers/merge_test.py b/tensorflow/python/keras/layers/merge_test.py index 7bcfcaeddb..698c5662b6 100644 --- a/tensorflow/python/keras/layers/merge_test.py +++ b/tensorflow/python/keras/layers/merge_test.py @@ -26,9 +26,9 @@ from tensorflow.python.ops import array_ops from tensorflow.python.platform import test +@tf_test_util.run_all_in_graph_and_eager_modes class MergeLayersTest(test.TestCase): - @tf_test_util.run_in_graph_and_eager_modes def test_merge_add(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) @@ -45,25 +45,6 @@ class MergeLayersTest(test.TestCase): self.assertEqual(out.shape, (2, 4, 5)) self.assertAllClose(out, x1 + x2 + x3, atol=1e-4) - def test_merge_add_masking(self): - with self.cached_session(): - i1 = keras.layers.Input(shape=(4, 5)) - i2 = keras.layers.Input(shape=(4, 5)) - m1 = keras.layers.Masking()(i1) - layer = keras.layers.Add() - o = layer([m1, i2]) - self.assertListEqual(o.get_shape().as_list(), [None, 4, 5]) - mask = layer.output_mask - self.assertListEqual(mask.get_shape().as_list(), [None, 4]) - - def test_merge_add_dynamic_shape(self): - with self.cached_session(): - i1 = array_ops.placeholder(shape=(4, None), dtype='float32') - i2 = array_ops.placeholder(shape=(4, 5), dtype='float32') - layer = keras.layers.Add() - o = layer([i1, i2]) - self.assertListEqual(o.get_shape().as_list(), [4, 5]) - def test_merge_elementwise_errors(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 6)) @@ -76,7 +57,6 @@ class MergeLayersTest(test.TestCase): with self.assertRaises(ValueError): keras.layers.add([i1]) - @tf_test_util.run_in_graph_and_eager_modes def test_merge_multiply(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) @@ -92,7 +72,6 @@ class MergeLayersTest(test.TestCase): self.assertEqual(out.shape, (2, 4, 5)) self.assertAllClose(out, x1 * x2 * x3, atol=1e-4) - @tf_test_util.run_in_graph_and_eager_modes def test_merge_average(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) @@ -106,7 +85,6 @@ class MergeLayersTest(test.TestCase): self.assertEqual(out.shape, (2, 4, 5)) self.assertAllClose(out, 0.5 * (x1 + x2), atol=1e-4) - @tf_test_util.run_in_graph_and_eager_modes def test_merge_maximum(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) @@ -120,7 +98,6 @@ class MergeLayersTest(test.TestCase): self.assertEqual(out.shape, (2, 4, 5)) self.assertAllClose(out, np.maximum(x1, x2), atol=1e-4) - @tf_test_util.run_in_graph_and_eager_modes def test_merge_minimum(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) @@ -134,7 +111,6 @@ class MergeLayersTest(test.TestCase): self.assertEqual(out.shape, (2, 4, 5)) self.assertAllClose(out, np.minimum(x1, x2), atol=1e-4) - @tf_test_util.run_in_graph_and_eager_modes def test_merge_concatenate(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) @@ -148,17 +124,6 @@ class MergeLayersTest(test.TestCase): self.assertEqual(out.shape, (2, 8, 5)) self.assertAllClose(out, np.concatenate([x1, x2], axis=1), atol=1e-4) - def test_merge_concatenate_masking(self): - with self.cached_session(): - i1 = keras.layers.Input(shape=(4, 5)) - i2 = keras.layers.Input(shape=(4, 5)) - m1 = keras.layers.Masking()(i1) - layer = keras.layers.Concatenate() - o = layer([m1, i2]) - self.assertListEqual(o.get_shape().as_list(), [None, 4, 10]) - mask = layer.output_mask - self.assertListEqual(mask.get_shape().as_list(), [None, 4]) - def test_concatenate_errors(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(3, 5)) @@ -169,7 +134,6 @@ class MergeLayersTest(test.TestCase): with self.assertRaisesRegexp(ValueError, 'called on a list'): keras.layers.concatenate([i1], axis=-1) - @tf_test_util.run_in_graph_and_eager_modes def test_merge_dot(self): i1 = keras.layers.Input(shape=(4,)) i2 = keras.layers.Input(shape=(4,)) @@ -215,7 +179,6 @@ class MergeLayersTest(test.TestCase): dot = keras.layers.Dot(1) dot.compute_output_shape(1) - @tf_test_util.run_in_graph_and_eager_modes def test_merge_subtract(self): i1 = keras.layers.Input(shape=(4, 5)) i2 = keras.layers.Input(shape=(4, 5)) @@ -231,5 +194,38 @@ class MergeLayersTest(test.TestCase): keras.layers.subtract([i1, i1, i1]) +class MergeLayersGraphOnlyTest(test.TestCase): + + def test_merge_add_masking(self): + with self.cached_session(): + i1 = keras.layers.Input(shape=(4, 5)) + i2 = keras.layers.Input(shape=(4, 5)) + m1 = keras.layers.Masking()(i1) + layer = keras.layers.Add() + o = layer([m1, i2]) + self.assertListEqual(o.get_shape().as_list(), [None, 4, 5]) + mask = layer.output_mask + self.assertListEqual(mask.get_shape().as_list(), [None, 4]) + + def test_merge_add_dynamic_shape(self): + with self.cached_session(): + i1 = array_ops.placeholder(shape=(4, None), dtype='float32') + i2 = array_ops.placeholder(shape=(4, 5), dtype='float32') + layer = keras.layers.Add() + o = layer([i1, i2]) + self.assertListEqual(o.get_shape().as_list(), [4, 5]) + + def test_merge_concatenate_masking(self): + with self.cached_session(): + i1 = keras.layers.Input(shape=(4, 5)) + i2 = keras.layers.Input(shape=(4, 5)) + m1 = keras.layers.Masking()(i1) + layer = keras.layers.Concatenate() + o = layer([m1, i2]) + self.assertListEqual(o.get_shape().as_list(), [None, 4, 10]) + mask = layer.output_mask + self.assertListEqual(mask.get_shape().as_list(), [None, 4]) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/layers/noise_test.py b/tensorflow/python/keras/layers/noise_test.py index cea304680b..325dd933b2 100644 --- a/tensorflow/python/keras/layers/noise_test.py +++ b/tensorflow/python/keras/layers/noise_test.py @@ -24,23 +24,21 @@ from tensorflow.python.keras import testing_utils from tensorflow.python.platform import test +@tf_test_util.run_all_in_graph_and_eager_modes class NoiseLayersTest(test.TestCase): def test_GaussianNoise(self): - with self.cached_session(): - testing_utils.layer_test( - keras.layers.GaussianNoise, - kwargs={'stddev': 1.}, - input_shape=(3, 2, 3)) + testing_utils.layer_test( + keras.layers.GaussianNoise, + kwargs={'stddev': 1.}, + input_shape=(3, 2, 3)) def test_GaussianDropout(self): - with self.cached_session(): - testing_utils.layer_test( - keras.layers.GaussianDropout, - kwargs={'rate': 0.5}, - input_shape=(3, 2, 3)) + testing_utils.layer_test( + keras.layers.GaussianDropout, + kwargs={'rate': 0.5}, + input_shape=(3, 2, 3)) - @tf_test_util.run_in_graph_and_eager_modes def test_AlphaDropout(self): testing_utils.layer_test( keras.layers.AlphaDropout, diff --git a/tensorflow/python/keras/layers/normalization_test.py b/tensorflow/python/keras/layers/normalization_test.py index ff705183ef..b11a350dbf 100644 --- a/tensorflow/python/keras/layers/normalization_test.py +++ b/tensorflow/python/keras/layers/normalization_test.py @@ -21,97 +21,97 @@ from __future__ import print_function import numpy as np from tensorflow.python import keras +from tensorflow.python.framework import test_util as tf_test_util from tensorflow.python.keras import testing_utils from tensorflow.python.platform import test +from tensorflow.python.training import gradient_descent +@tf_test_util.run_all_in_graph_and_eager_modes class NormalizationLayersTest(test.TestCase): def test_basic_batchnorm(self): - with self.cached_session(): - testing_utils.layer_test( - keras.layers.BatchNormalization, - kwargs={ - 'momentum': 0.9, - 'epsilon': 0.1, - 'gamma_regularizer': keras.regularizers.l2(0.01), - 'beta_regularizer': keras.regularizers.l2(0.01) - }, - input_shape=(3, 4, 2)) - testing_utils.layer_test( - keras.layers.BatchNormalization, - kwargs={ - 'gamma_initializer': 'ones', - 'beta_initializer': 'ones', - 'moving_mean_initializer': 'zeros', - 'moving_variance_initializer': 'ones' - }, - input_shape=(3, 4, 2)) - testing_utils.layer_test( - keras.layers.BatchNormalization, - kwargs={'scale': False, - 'center': False}, - input_shape=(3, 3)) + testing_utils.layer_test( + keras.layers.BatchNormalization, + kwargs={ + 'momentum': 0.9, + 'epsilon': 0.1, + 'gamma_regularizer': keras.regularizers.l2(0.01), + 'beta_regularizer': keras.regularizers.l2(0.01) + }, + input_shape=(3, 4, 2)) + testing_utils.layer_test( + keras.layers.BatchNormalization, + kwargs={ + 'gamma_initializer': 'ones', + 'beta_initializer': 'ones', + 'moving_mean_initializer': 'zeros', + 'moving_variance_initializer': 'ones' + }, + input_shape=(3, 4, 2)) + testing_utils.layer_test( + keras.layers.BatchNormalization, + kwargs={'scale': False, + 'center': False}, + input_shape=(3, 3)) def test_batchnorm_weights(self): - with self.cached_session(): - layer = keras.layers.BatchNormalization(scale=False, center=False) - layer.build((None, 3, 4)) - self.assertEqual(len(layer.trainable_weights), 0) - self.assertEqual(len(layer.weights), 2) + layer = keras.layers.BatchNormalization(scale=False, center=False) + layer.build((None, 3, 4)) + self.assertEqual(len(layer.trainable_weights), 0) + self.assertEqual(len(layer.weights), 2) - layer = keras.layers.BatchNormalization() - layer.build((None, 3, 4)) - self.assertEqual(len(layer.trainable_weights), 2) - self.assertEqual(len(layer.weights), 4) + layer = keras.layers.BatchNormalization() + layer.build((None, 3, 4)) + self.assertEqual(len(layer.trainable_weights), 2) + self.assertEqual(len(layer.weights), 4) def test_batchnorm_regularization(self): - with self.cached_session(): - layer = keras.layers.BatchNormalization( - gamma_regularizer='l1', beta_regularizer='l1') - layer.build((None, 3, 4)) - self.assertEqual(len(layer.losses), 2) - max_norm = keras.constraints.max_norm - layer = keras.layers.BatchNormalization( - gamma_constraint=max_norm, beta_constraint=max_norm) - layer.build((None, 3, 4)) - self.assertEqual(layer.gamma.constraint, max_norm) - self.assertEqual(layer.beta.constraint, max_norm) + layer = keras.layers.BatchNormalization( + gamma_regularizer='l1', beta_regularizer='l1') + layer.build((None, 3, 4)) + self.assertEqual(len(layer.losses), 2) + max_norm = keras.constraints.max_norm + layer = keras.layers.BatchNormalization( + gamma_constraint=max_norm, beta_constraint=max_norm) + layer.build((None, 3, 4)) + self.assertEqual(layer.gamma.constraint, max_norm) + self.assertEqual(layer.beta.constraint, max_norm) def test_batchnorm_correctness(self): - with self.cached_session(): - model = keras.models.Sequential() - norm = keras.layers.BatchNormalization(input_shape=(10,), momentum=0.8) - model.add(norm) - model.compile(loss='mse', optimizer='sgd') - - # centered on 5.0, variance 10.0 - x = np.random.normal(loc=5.0, scale=10.0, size=(1000, 10)) - model.fit(x, x, epochs=4, verbose=0) - out = model.predict(x) - out -= keras.backend.eval(norm.beta) - out /= keras.backend.eval(norm.gamma) - - np.testing.assert_allclose(out.mean(), 0.0, atol=1e-1) - np.testing.assert_allclose(out.std(), 1.0, atol=1e-1) + model = keras.models.Sequential() + norm = keras.layers.BatchNormalization(input_shape=(10,), momentum=0.8) + model.add(norm) + model.compile(loss='mse', + optimizer=gradient_descent.GradientDescentOptimizer(0.01)) + + # centered on 5.0, variance 10.0 + x = np.random.normal(loc=5.0, scale=10.0, size=(1000, 10)) + model.fit(x, x, epochs=4, verbose=0) + out = model.predict(x) + out -= keras.backend.eval(norm.beta) + out /= keras.backend.eval(norm.gamma) + + np.testing.assert_allclose(out.mean(), 0.0, atol=1e-1) + np.testing.assert_allclose(out.std(), 1.0, atol=1e-1) def test_batchnorm_mixed_precision(self): - with self.cached_session(): - model = keras.models.Sequential() - norm = keras.layers.BatchNormalization(input_shape=(10,), momentum=0.8) - model.add(norm) - model.compile(loss='mse', optimizer='sgd') - - # centered on 5.0, variance 10.0 - x = np.random.normal( - loc=5.0, scale=10.0, size=(1000, 10)).astype(np.float16) - model.fit(x, x, epochs=4, verbose=0) - out = model.predict(x) - out -= keras.backend.eval(norm.beta) - out /= keras.backend.eval(norm.gamma) - - np.testing.assert_allclose(out.mean(), 0.0, atol=1e-1) - np.testing.assert_allclose(out.std(), 1.0, atol=1e-1) + model = keras.models.Sequential() + norm = keras.layers.BatchNormalization(input_shape=(10,), momentum=0.8) + model.add(norm) + model.compile(loss='mse', + optimizer=gradient_descent.GradientDescentOptimizer(0.01)) + + # centered on 5.0, variance 10.0 + x = np.random.normal( + loc=5.0, scale=10.0, size=(1000, 10)).astype(np.float16) + model.fit(x, x, epochs=4, verbose=0) + out = model.predict(x) + out -= keras.backend.eval(norm.beta) + out /= keras.backend.eval(norm.gamma) + + np.testing.assert_allclose(out.mean(), 0.0, atol=1e-1) + np.testing.assert_allclose(out.std(), 1.0, atol=1e-1) def test_batchnorm_convnet(self): if test.is_gpu_available(cuda_only=True): @@ -120,7 +120,8 @@ class NormalizationLayersTest(test.TestCase): norm = keras.layers.BatchNormalization( axis=1, input_shape=(3, 4, 4), momentum=0.8) model.add(norm) - model.compile(loss='mse', optimizer='sgd') + model.compile(loss='mse', + optimizer=gradient_descent.GradientDescentOptimizer(0.01)) # centered on 5.0, variance 10.0 x = np.random.normal(loc=5.0, scale=10.0, size=(1000, 3, 4, 4)) @@ -133,24 +134,27 @@ class NormalizationLayersTest(test.TestCase): np.testing.assert_allclose(np.std(out, axis=(0, 2, 3)), 1.0, atol=1e-1) def test_batchnorm_convnet_channel_last(self): - with self.cached_session(): - # keras.backend.set_learning_phase(True) + # keras.backend.set_learning_phase(True) + + model = keras.models.Sequential() + norm = keras.layers.BatchNormalization( + axis=-1, input_shape=(4, 4, 3), momentum=0.8) + model.add(norm) + model.compile(loss='mse', + optimizer=gradient_descent.GradientDescentOptimizer(0.01)) + + # centered on 5.0, variance 10.0 + x = np.random.normal(loc=5.0, scale=10.0, size=(1000, 4, 4, 3)) + model.fit(x, x, epochs=4, verbose=0) + out = model.predict(x) + out -= np.reshape(keras.backend.eval(norm.beta), (1, 1, 1, 3)) + out /= np.reshape(keras.backend.eval(norm.gamma), (1, 1, 1, 3)) - model = keras.models.Sequential() - norm = keras.layers.BatchNormalization( - axis=-1, input_shape=(4, 4, 3), momentum=0.8) - model.add(norm) - model.compile(loss='mse', optimizer='sgd') + np.testing.assert_allclose(np.mean(out, axis=(0, 1, 2)), 0.0, atol=1e-1) + np.testing.assert_allclose(np.std(out, axis=(0, 1, 2)), 1.0, atol=1e-1) - # centered on 5.0, variance 10.0 - x = np.random.normal(loc=5.0, scale=10.0, size=(1000, 4, 4, 3)) - model.fit(x, x, epochs=4, verbose=0) - out = model.predict(x) - out -= np.reshape(keras.backend.eval(norm.beta), (1, 1, 1, 3)) - out /= np.reshape(keras.backend.eval(norm.gamma), (1, 1, 1, 3)) - np.testing.assert_allclose(np.mean(out, axis=(0, 1, 2)), 0.0, atol=1e-1) - np.testing.assert_allclose(np.std(out, axis=(0, 1, 2)), 1.0, atol=1e-1) +class NormalizationLayersGraphModeOnlyTest(test.TestCase): def test_shared_batchnorm(self): """Test that a BN layer can be shared across different data streams. @@ -167,7 +171,7 @@ class NormalizationLayersTest(test.TestCase): x = np.random.normal(loc=5.0, scale=10.0, size=(2, 10)) model = keras.models.Model(x2, y2) - model.compile('sgd', 'mse') + model.compile(gradient_descent.GradientDescentOptimizer(0.01), 'mse') model.train_on_batch(x, x) self.assertEqual(len(bn.updates), 4) @@ -183,7 +187,7 @@ class NormalizationLayersTest(test.TestCase): self.assertEqual(len(new_model.updates), 2) self.assertEqual(len(model.updates), 4) self.assertEqual(len(new_model.get_updates_for(x3)), 2) - new_model.compile('sgd', 'mse') + new_model.compile(gradient_descent.GradientDescentOptimizer(0.01), 'mse') new_model.train_on_batch(x, x) def test_that_trainable_disables_updates(self): @@ -199,7 +203,7 @@ class NormalizationLayersTest(test.TestCase): model.trainable = False assert not model.updates - model.compile('sgd', 'mse') + model.compile(gradient_descent.GradientDescentOptimizer(0.01), 'mse') assert not model.updates x1 = model.predict(val_a) @@ -208,7 +212,7 @@ class NormalizationLayersTest(test.TestCase): self.assertAllClose(x1, x2, atol=1e-7) model.trainable = True - model.compile('sgd', 'mse') + model.compile(gradient_descent.GradientDescentOptimizer(0.01), 'mse') assert model.updates model.train_on_batch(val_a, val_out) @@ -216,7 +220,7 @@ class NormalizationLayersTest(test.TestCase): assert np.abs(np.sum(x1 - x2)) > 1e-5 layer.trainable = False - model.compile('sgd', 'mse') + model.compile(gradient_descent.GradientDescentOptimizer(0.01), 'mse') assert not model.updates x1 = model.predict(val_a) diff --git a/tensorflow/python/keras/layers/serialization_test.py b/tensorflow/python/keras/layers/serialization_test.py index 5872185ef7..548c3ec1ac 100644 --- a/tensorflow/python/keras/layers/serialization_test.py +++ b/tensorflow/python/keras/layers/serialization_test.py @@ -19,9 +19,11 @@ from __future__ import division from __future__ import print_function from tensorflow.python import keras +from tensorflow.python.framework import test_util as tf_test_util from tensorflow.python.platform import test +@tf_test_util.run_all_in_graph_and_eager_modes class LayerSerializationTest(test.TestCase): def test_serialize_deserialize(self): diff --git a/tensorflow/python/keras/layers/simplernn_test.py b/tensorflow/python/keras/layers/simplernn_test.py index 2f2295a793..93456b5e3a 100644 --- a/tensorflow/python/keras/layers/simplernn_test.py +++ b/tensorflow/python/keras/layers/simplernn_test.py @@ -24,12 +24,13 @@ from tensorflow.python import keras from tensorflow.python.framework import test_util as tf_test_util from tensorflow.python.keras import testing_utils from tensorflow.python.platform import test +from tensorflow.python.training import gradient_descent from tensorflow.python.training.rmsprop import RMSPropOptimizer +@tf_test_util.run_all_in_graph_and_eager_modes class SimpleRNNLayerTest(test.TestCase): - @tf_test_util.run_in_graph_and_eager_modes def test_return_sequences_SimpleRNN(self): num_samples = 2 timesteps = 3 @@ -41,7 +42,6 @@ class SimpleRNNLayerTest(test.TestCase): 'return_sequences': True}, input_shape=(num_samples, timesteps, embedding_dim)) - @tf_test_util.run_in_graph_and_eager_modes def test_dynamic_behavior_SimpleRNN(self): num_samples = 2 timesteps = 3 @@ -55,7 +55,6 @@ class SimpleRNNLayerTest(test.TestCase): y = np.random.random((num_samples, units)) model.train_on_batch(x, y) - @tf_test_util.run_in_graph_and_eager_modes def test_dropout_SimpleRNN(self): num_samples = 2 timesteps = 3 @@ -68,7 +67,6 @@ class SimpleRNNLayerTest(test.TestCase): 'recurrent_dropout': 0.1}, input_shape=(num_samples, timesteps, embedding_dim)) - @tf_test_util.run_in_graph_and_eager_modes def test_implementation_mode_SimpleRNN(self): num_samples = 2 timesteps = 3 @@ -81,6 +79,47 @@ class SimpleRNNLayerTest(test.TestCase): 'implementation': mode}, input_shape=(num_samples, timesteps, embedding_dim)) + def test_constraints_SimpleRNN(self): + embedding_dim = 4 + layer_class = keras.layers.SimpleRNN + 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_with_masking_layer_SimpleRNN(self): + layer_class = keras.layers.SimpleRNN + inputs = np.random.random((2, 3, 4)) + targets = np.abs(np.random.random((2, 3, 5))) + targets /= targets.sum(axis=-1, keepdims=True) + model = keras.models.Sequential() + model.add(keras.layers.Masking(input_shape=(3, 4))) + model.add(layer_class(units=5, return_sequences=True, unroll=False)) + model.compile(loss='categorical_crossentropy', + optimizer=RMSPropOptimizer(0.01)) + model.fit(inputs, targets, epochs=1, batch_size=2, verbose=1) + + def test_from_config_SimpleRNN(self): + layer_class = keras.layers.SimpleRNN + 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() + + +class SimpleRNNLayerGraphOnlyTest(test.TestCase): + def test_statefulness_SimpleRNN(self): num_samples = 2 timesteps = 3 @@ -99,7 +138,8 @@ class SimpleRNNLayerTest(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=gradient_descent.GradientDescentOptimizer(0.01), + loss='mse') out1 = model.predict(np.ones((num_samples, timesteps))) self.assertEqual(out1.shape, (num_samples, units)) @@ -163,47 +203,5 @@ class SimpleRNNLayerTest(test.TestCase): layer(x) self.assertEqual(len(layer.get_losses_for(x)), 1) - def test_constraints_SimpleRNN(self): - embedding_dim = 4 - layer_class = keras.layers.SimpleRNN - with self.cached_session(): - 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) - - @tf_test_util.run_in_graph_and_eager_modes - def test_with_masking_layer_SimpleRNN(self): - layer_class = keras.layers.SimpleRNN - with self.cached_session(): - inputs = np.random.random((2, 3, 4)) - targets = np.abs(np.random.random((2, 3, 5))) - targets /= targets.sum(axis=-1, keepdims=True) - model = keras.models.Sequential() - model.add(keras.layers.Masking(input_shape=(3, 4))) - model.add(layer_class(units=5, return_sequences=True, unroll=False)) - model.compile(loss='categorical_crossentropy', - optimizer=RMSPropOptimizer(0.01)) - model.fit(inputs, targets, epochs=1, batch_size=2, verbose=1) - - def test_from_config_SimpleRNN(self): - layer_class = keras.layers.SimpleRNN - 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() - - if __name__ == '__main__': test.main() -- GitLab From ead9f381d6eb96d075b4c3c7b1c22a04c4118842 Mon Sep 17 00:00:00 2001 From: Alexey Radul Date: Tue, 16 Oct 2018 18:23:05 -0700 Subject: [PATCH 0186/1825] Internal change. PiperOrigin-RevId: 217423671 --- tensorflow/python/autograph/pyct/common_transformers/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/python/autograph/pyct/common_transformers/BUILD b/tensorflow/python/autograph/pyct/common_transformers/BUILD index 1106a19de1..5e2f8f3ac0 100644 --- a/tensorflow/python/autograph/pyct/common_transformers/BUILD +++ b/tensorflow/python/autograph/pyct/common_transformers/BUILD @@ -34,7 +34,6 @@ py_test( name = "anf_test", srcs = ["anf_test.py"], srcs_version = "PY2AND3", - tags = ["no_oss"], deps = [ ":common_transformers", "//tensorflow/python:client_testlib", -- GitLab From ecc910875ded2f1205bf392bacf59ee3b701ebe3 Mon Sep 17 00:00:00 2001 From: Shivani Agrawal Date: Tue, 16 Oct 2018 18:35:56 -0700 Subject: [PATCH 0187/1825] set_use_resource back to default value in defun to unbreak existing test cases around variable_scope. PiperOrigin-RevId: 217424918 --- tensorflow/python/eager/function.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 6b37ab9410..5fd49dd979 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -924,7 +924,9 @@ def func_graph_from_py_func(name, else: control_manager = ops.NullContextmanager with func_graph.as_default(), control_manager() as a: - variable_scope.get_variable_scope().set_use_resource(True) + current_scope = variable_scope.get_variable_scope() + default_use_recource = current_scope.use_resource + current_scope.set_use_resource(True) if signature is not None: args = signature @@ -976,6 +978,7 @@ def func_graph_from_py_func(name, check_mutation(func_kwargs_before, func_kwargs) finally: tape.pop_tape(this_tape) + current_scope.set_use_resource(default_use_recource) # Variables in `func_args`, `func_kwargs` should be explicit inputs # to the function, not captured inputs. -- GitLab From dc7ca1cc490b9efbd907553dff133df933591c01 Mon Sep 17 00:00:00 2001 From: David Majnemer Date: Tue, 16 Oct 2018 19:17:29 -0700 Subject: [PATCH 0188/1825] Internal change PiperOrigin-RevId: 217428291 --- .../compiler/xla/tests/reduce_precision_test.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/xla/tests/reduce_precision_test.cc b/tensorflow/compiler/xla/tests/reduce_precision_test.cc index 26e2bfde5c..193e669692 100644 --- a/tensorflow/compiler/xla/tests/reduce_precision_test.cc +++ b/tensorflow/compiler/xla/tests/reduce_precision_test.cc @@ -283,7 +283,7 @@ XLA_TEST_F(ReducePrecisionInsertionTest, DISABLED_ON_INTERPRETER(ReducePrecisionSkippedAfterFusion)) { XlaBuilder builder(TestName()); - Literal a_literal = LiteralUtil::CreateR1({1.00001}); + Literal a_literal = LiteralUtil::CreateR1({1.00001, 1.00001}); std::unique_ptr a_data = client_->TransferToServer(a_literal).ConsumeValueOrDie(); auto a = Parameter(&builder, 0, a_literal.shape(), "a"); @@ -301,7 +301,7 @@ XLA_TEST_F(ReducePrecisionInsertionTest, HloReducePrecisionOptions::UNFUSED_OP_OUTPUTS, 5, 10, [](const HloOpcode opcode) { return opcode == HloOpcode::kAbs; }); - ComputeAndCompareR1(&builder, {-1.00001f}, {a_data.get()}); + ComputeAndCompareR1(&builder, {-1.00001f, -1.00001f}, {a_data.get()}); } // The interpreter has no fusion pass, so skip this test. @@ -309,7 +309,7 @@ XLA_TEST_F(ReducePrecisionInsertionTest, DISABLED_ON_INTERPRETER(ReducePrecisionAddedAfterFusion)) { XlaBuilder builder(TestName()); - Literal a_literal = LiteralUtil::CreateR1({1.00001}); + Literal a_literal = LiteralUtil::CreateR1({1.00001, 1.00001}); std::unique_ptr a_data = client_->TransferToServer(a_literal).ConsumeValueOrDie(); auto a = Parameter(&builder, 0, a_literal.shape(), "a"); @@ -325,7 +325,7 @@ XLA_TEST_F(ReducePrecisionInsertionTest, HloReducePrecisionOptions::UNFUSED_OP_OUTPUTS, 5, 10, [](const HloOpcode opcode) { return opcode == HloOpcode::kFusion; }); - ComputeAndCompareR1(&builder, {-1.0f}, {a_data.get()}); + ComputeAndCompareR1(&builder, {-1.0f, -1.0f}, {a_data.get()}); } // The interpreter has no fusion pass, so skip this test. @@ -358,7 +358,7 @@ XLA_TEST_F(ReducePrecisionInsertionTest, DISABLED_ON_INTERPRETER(ReducePrecisionAddedFusionContains)) { XlaBuilder builder(TestName()); - Literal a_literal = LiteralUtil::CreateR1({1.00001}); + Literal a_literal = LiteralUtil::CreateR1({1.00001, 1.00001}); std::unique_ptr a_data = client_->TransferToServer(a_literal).ConsumeValueOrDie(); auto a = Parameter(&builder, 0, a_literal.shape(), "a"); @@ -375,7 +375,7 @@ XLA_TEST_F(ReducePrecisionInsertionTest, HloReducePrecisionOptions::FUSION_OUTPUTS_BY_CONTENT, 5, 10, [](const HloOpcode opcode) { return opcode == HloOpcode::kAbs; }); - ComputeAndCompareR1(&builder, {-1.0f}, {a_data.get()}); + ComputeAndCompareR1(&builder, {-1.0f, -1.0f}, {a_data.get()}); } } // namespace -- GitLab From 0f1894de6cfddb3df8a3d6865d97b947af56f995 Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Tue, 16 Oct 2018 19:57:07 -0700 Subject: [PATCH 0189/1825] Move BUILD file to optimizer_v2 subfolder. PiperOrigin-RevId: 217430948 --- tensorflow/contrib/optimizer_v2/BUILD | 2 +- tensorflow/python/keras/BUILD | 156 +------------------- tensorflow/python/keras/optimizer_v2/BUILD | 164 +++++++++++++++++++++ 3 files changed, 166 insertions(+), 156 deletions(-) create mode 100644 tensorflow/python/keras/optimizer_v2/BUILD diff --git a/tensorflow/contrib/optimizer_v2/BUILD b/tensorflow/contrib/optimizer_v2/BUILD index 2cf445a85e..0700b7c73c 100644 --- a/tensorflow/contrib/optimizer_v2/BUILD +++ b/tensorflow/contrib/optimizer_v2/BUILD @@ -48,7 +48,7 @@ py_library( srcs_version = "PY2AND3", deps = [ "//tensorflow/python:util", - "//tensorflow/python/keras:optimizer_v2", + "//tensorflow/python/keras/optimizer_v2", ], ) diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index a566c9acab..7b57871e77 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -62,7 +62,7 @@ py_library( ":backend", ":engine", ":layers", - ":optimizer_v2", + "//tensorflow/python/keras/optimizer_v2:optimizer_v2", "//tensorflow/python/saved_model", "//tensorflow/python:training", ], @@ -190,30 +190,6 @@ py_library( ], ) -py_library( - name = "optimizer_v2", - srcs = [ - "optimizer_v2/adadelta.py", - "optimizer_v2/adagrad.py", - "optimizer_v2/adam.py", - "optimizer_v2/optimizer_v2.py", - "optimizer_v2/rmsprop.py", - "optimizer_v2/sgd.py", - ], - srcs_version = "PY2AND3", - deps = [ - "//tensorflow/python:control_flow_ops", - "//tensorflow/python:distribute", - "//tensorflow/python:framework", - "//tensorflow/python:math_ops", - "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:state_ops", - "//tensorflow/python:training", - "//tensorflow/python:variable_scope", - "//tensorflow/python:variables", - ], -) - py_test( name = "integration_test", size = "medium", @@ -865,133 +841,3 @@ py_library( "//third_party/py/numpy", ], ) - -cuda_py_test( - name = "adadelta_test", - size = "medium", - srcs = ["optimizer_v2/adadelta_test.py"], - additional_deps = [ - ":optimizer_v2", - "//tensorflow/python:client_testlib", - "//tensorflow/python:embedding_ops", - "//tensorflow/python:framework", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform", - "//tensorflow/python:platform_test", - "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:variables", - "//third_party/py/numpy", - ], -) - -cuda_py_test( - name = "adagrad_test", - size = "small", - srcs = ["optimizer_v2/adagrad_test.py"], - additional_deps = [ - ":optimizer_v2", - "//tensorflow/python:embedding_ops", - "//tensorflow/python:framework", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform", - "//tensorflow/python:platform_test", - "//tensorflow/python:client_testlib", - "//third_party/py/numpy", - ], -) - -cuda_py_test( - name = "adam_test", - size = "small", - srcs = ["optimizer_v2/adam_test.py"], - additional_deps = [ - ":optimizer_v2", - "//tensorflow/python:array_ops", - "//tensorflow/python:framework", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform", - "//tensorflow/python:platform_test", - "//tensorflow/python:client_testlib", - "//third_party/py/numpy", - ], -) - -cuda_py_test( - name = "checkpointable_utils_test", - srcs = ["optimizer_v2/checkpointable_utils_test.py"], - additional_deps = [ - ":optimizer_v2", - "@six_archive//:six", - "//tensorflow/python:constant_op", - "//tensorflow/python:dtypes", - "//tensorflow/python:framework_ops", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:init_ops", - "//tensorflow/python:layers", - "//tensorflow/python:layers_base", - "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:state_ops", - "//tensorflow/python:training", - "//tensorflow/python:variable_scope", - "//tensorflow/python:variables", - "//tensorflow/python/eager:context", - "//tensorflow/python/eager:test", - "//tensorflow/python/keras", - ], - tags = ["notsan"], -) - -cuda_py_test( - name = "sgd_test", - size = "medium", - srcs = ["optimizer_v2/sgd_test.py"], - additional_deps = [ - ":optimizer_v2", - "//tensorflow/python:client_testlib", - "//tensorflow/python:embedding_ops", - "//tensorflow/python:platform_test", - "//tensorflow/python:framework", - "//tensorflow/python:math_ops", - "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:resources", - "//tensorflow/python:variables", - "//tensorflow/python/eager:context", - ], -) - -cuda_py_test( - name = "optimizer_v2_test", - size = "medium", - srcs = ["optimizer_v2/optimizer_v2_test.py"], - additional_deps = [ - ":optimizer_v2", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:array_ops", - "//tensorflow/python:clip_ops", - "//tensorflow/python:gradients", - "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:state_ops", - "//tensorflow/python:variables", - ], -) - -cuda_py_test( - name = "rmsprop_test", - size = "small", - srcs = ["optimizer_v2/rmsprop_test.py"], - additional_deps = [ - ":optimizer_v2", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:array_ops", - "//tensorflow/python:embedding_ops", - "//tensorflow/python:framework", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform", - "//tensorflow/python:platform_test", - "//tensorflow/python:client_testlib", - "//third_party/py/numpy", - ], - tags = ["optonly"], -) diff --git a/tensorflow/python/keras/optimizer_v2/BUILD b/tensorflow/python/keras/optimizer_v2/BUILD new file mode 100644 index 0000000000..292c717e36 --- /dev/null +++ b/tensorflow/python/keras/optimizer_v2/BUILD @@ -0,0 +1,164 @@ +# Description: +# Contains the Keras OptimizerV2 API (internal TensorFlow version). + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +load("//tensorflow:tensorflow.bzl", "cuda_py_test") + +py_library( + name = "optimizer_v2", + srcs = [ + "adadelta.py", + "adagrad.py", + "adam.py", + "optimizer_v2.py", + "rmsprop.py", + "sgd.py", + ], + srcs_version = "PY2AND3", + deps = [ + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:distribute", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:state_ops", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", + ], +) + +cuda_py_test( + name = "adadelta_test", + size = "medium", + srcs = ["adadelta_test.py"], + additional_deps = [ + ":optimizer_v2", + "//tensorflow/python:client_testlib", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:platform_test", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:variables", + "//third_party/py/numpy", + ], +) + +cuda_py_test( + name = "adagrad_test", + size = "small", + srcs = ["adagrad_test.py"], + additional_deps = [ + ":optimizer_v2", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:platform_test", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], +) + +cuda_py_test( + name = "adam_test", + size = "small", + srcs = ["adam_test.py"], + additional_deps = [ + ":optimizer_v2", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:platform_test", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], +) + +cuda_py_test( + name = "checkpointable_utils_test", + srcs = ["checkpointable_utils_test.py"], + additional_deps = [ + ":optimizer_v2", + "@six_archive//:six", + "//tensorflow/python:constant_op", + "//tensorflow/python:dtypes", + "//tensorflow/python:framework_ops", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:init_ops", + "//tensorflow/python:layers", + "//tensorflow/python:layers_base", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:state_ops", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:test", + "//tensorflow/python/keras", + ], + tags = ["notsan"], +) + +cuda_py_test( + name = "sgd_test", + size = "medium", + srcs = ["sgd_test.py"], + additional_deps = [ + ":optimizer_v2", + "//tensorflow/python:client_testlib", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:resources", + "//tensorflow/python:variables", + "//tensorflow/python/eager:context", + ], +) + +cuda_py_test( + name = "optimizer_v2_test", + size = "medium", + srcs = ["optimizer_v2_test.py"], + additional_deps = [ + ":optimizer_v2", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:array_ops", + "//tensorflow/python:clip_ops", + "//tensorflow/python:gradients", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:state_ops", + "//tensorflow/python:variables", + ], +) + +cuda_py_test( + name = "rmsprop_test", + size = "small", + srcs = ["rmsprop_test.py"], + additional_deps = [ + ":optimizer_v2", + "@absl_py//absl/testing:parameterized", + "//tensorflow/python:array_ops", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:platform_test", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], + tags = ["optonly"], +) -- GitLab From 3d209297c423ed0a9bf7ccdeab3a1b7c65c1ccb5 Mon Sep 17 00:00:00 2001 From: Geoffrey Irving Date: Tue, 16 Oct 2018 21:24:43 -0700 Subject: [PATCH 0190/1825] Resolve @annarev's comments I haven't tested this under Python 2, so here's hoping all the golden rewrites were done correctly. --- ...nsorflow.data.-dataset.__metaclass__.pbtxt | 2 +- ...-length-record-dataset.__metaclass__.pbtxt | 2 +- ...ta.-t-f-record-dataset.__metaclass__.pbtxt | 2 +- ...ata.-text-line-dataset.__metaclass__.pbtxt | 2 +- ...erimental.-csv-dataset.__metaclass__.pbtxt | 2 +- ...mental.-random-dataset.__metaclass__.pbtxt | 2 +- ...erimental.-sql-dataset.__metaclass__.pbtxt | 2 +- .../v1/tensorflow.data.experimental.pbtxt | 6 +- .../tools/api/golden/v1/tensorflow.data.pbtxt | 8 +-- ...-classification-output.__metaclass__.pbtxt | 2 +- ....export.-export-output.__metaclass__.pbtxt | 2 +- ...export.-predict-output.__metaclass__.pbtxt | 2 +- ...ort.-regression-output.__metaclass__.pbtxt | 2 +- .../v1/tensorflow.estimator.export.pbtxt | 8 +-- ...ar-operator-block-diag.__metaclass__.pbtxt | 2 +- ...ear-operator-circulant.__metaclass__.pbtxt | 2 +- ...-operator-circulant2-d.__metaclass__.pbtxt | 2 +- ...-operator-circulant3-d.__metaclass__.pbtxt | 2 +- ...r-operator-composition.__metaclass__.pbtxt | 2 +- ....-linear-operator-diag.__metaclass__.pbtxt | 2 +- ...r-operator-full-matrix.__metaclass__.pbtxt | 2 +- ...near-operator-identity.__metaclass__.pbtxt | 2 +- ...ear-operator-kronecker.__metaclass__.pbtxt | 2 +- ...erator-low-rank-update.__metaclass__.pbtxt | 2 +- ...rator-lower-triangular.__metaclass__.pbtxt | 2 +- ...erator-scaled-identity.__metaclass__.pbtxt | 2 +- ...-linear-operator-zeros.__metaclass__.pbtxt | 2 +- ...inalg.-linear-operator.__metaclass__.pbtxt | 2 +- .../api/golden/v1/tensorflow.linalg.pbtxt | 28 ++++----- ...rain.-nan-loss-during-training-error.pbtxt | 4 -- ...nsorflow.data.-dataset.__metaclass__.pbtxt | 2 +- ...-length-record-dataset.__metaclass__.pbtxt | 2 +- ...ta.-t-f-record-dataset.__metaclass__.pbtxt | 2 +- ...ata.-text-line-dataset.__metaclass__.pbtxt | 2 +- ...erimental.-csv-dataset.__metaclass__.pbtxt | 2 +- ...mental.-random-dataset.__metaclass__.pbtxt | 2 +- ...erimental.-sql-dataset.__metaclass__.pbtxt | 2 +- .../v2/tensorflow.data.experimental.pbtxt | 6 +- .../tools/api/golden/v2/tensorflow.data.pbtxt | 8 +-- ...-classification-output.__metaclass__.pbtxt | 2 +- ....export.-export-output.__metaclass__.pbtxt | 2 +- ...export.-predict-output.__metaclass__.pbtxt | 2 +- ...ort.-regression-output.__metaclass__.pbtxt | 2 +- .../v2/tensorflow.estimator.export.pbtxt | 8 +-- ...ar-operator-block-diag.__metaclass__.pbtxt | 2 +- ...ear-operator-circulant.__metaclass__.pbtxt | 2 +- ...-operator-circulant2-d.__metaclass__.pbtxt | 2 +- ...-operator-circulant3-d.__metaclass__.pbtxt | 2 +- ...r-operator-composition.__metaclass__.pbtxt | 2 +- ....-linear-operator-diag.__metaclass__.pbtxt | 2 +- ...r-operator-full-matrix.__metaclass__.pbtxt | 2 +- ...near-operator-identity.__metaclass__.pbtxt | 2 +- ...ear-operator-kronecker.__metaclass__.pbtxt | 2 +- ...erator-low-rank-update.__metaclass__.pbtxt | 2 +- ...rator-lower-triangular.__metaclass__.pbtxt | 2 +- ...erator-scaled-identity.__metaclass__.pbtxt | 2 +- ...-linear-operator-zeros.__metaclass__.pbtxt | 2 +- ...inalg.-linear-operator.__metaclass__.pbtxt | 2 +- .../api/golden/v2/tensorflow.linalg.pbtxt | 28 ++++----- ...rain.-nan-loss-during-training-error.pbtxt | 4 -- .../api/lib/python_object_to_proto_visitor.py | 61 +++++++------------ .../tools/api/tests/api_compatibility_test.py | 2 - 62 files changed, 123 insertions(+), 148 deletions(-) diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.-dataset.__metaclass__.pbtxt index af08c88d33..d81a3d986d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.Dataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.-fixed-length-record-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.-fixed-length-record-dataset.__metaclass__.pbtxt index f384323fc8..eb7c8dc264 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.-fixed-length-record-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.-fixed-length-record-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.FixedLengthRecordDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.-t-f-record-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.-t-f-record-dataset.__metaclass__.pbtxt index b12dec8a70..7cd273b2dd 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.-t-f-record-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.-t-f-record-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.TFRecordDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.-text-line-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.-text-line-dataset.__metaclass__.pbtxt index 7ddcdce266..b30f93ef5d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.-text-line-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.-text-line-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.TextLineDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-csv-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-csv-dataset.__metaclass__.pbtxt index 3eeaa1b185..604a1dc89e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-csv-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-csv-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.experimental.CsvDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-random-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-random-dataset.__metaclass__.pbtxt index 2991b12f64..0c2300a4da 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-random-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-random-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.experimental.RandomDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-sql-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-sql-dataset.__metaclass__.pbtxt index 948e99ef86..f1a96b03e5 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-sql-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.-sql-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.experimental.SqlDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt index f5f96ab98b..116684e5d8 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.experimental.pbtxt @@ -10,7 +10,7 @@ tf_module { } member { name: "CsvDataset" - mtype: "" + mtype: "" } member { name: "Optional" @@ -18,7 +18,7 @@ tf_module { } member { name: "RandomDataset" - mtype: "" + mtype: "" } member { name: "Reducer" @@ -26,7 +26,7 @@ tf_module { } member { name: "SqlDataset" - mtype: "" + mtype: "" } member { name: "StatsAggregator" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.data.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.data.pbtxt index 3023276a1d..509bbae833 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.data.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.data.pbtxt @@ -2,11 +2,11 @@ path: "tensorflow.data" tf_module { member { name: "Dataset" - mtype: "" + mtype: "" } member { name: "FixedLengthRecordDataset" - mtype: "" + mtype: "" } member { name: "Iterator" @@ -18,11 +18,11 @@ tf_module { } member { name: "TFRecordDataset" - mtype: "" + mtype: "" } member { name: "TextLineDataset" - mtype: "" + mtype: "" } member { name: "experimental" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-classification-output.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-classification-output.__metaclass__.pbtxt index 3cf7af8da9..820afac8e1 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-classification-output.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-classification-output.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.ClassificationOutput.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-export-output.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-export-output.__metaclass__.pbtxt index 5d165ccbf9..b811e1f3da 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-export-output.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-export-output.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.ExportOutput.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-predict-output.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-predict-output.__metaclass__.pbtxt index 743495ba98..bdfcb9c888 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-predict-output.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-predict-output.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.PredictOutput.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-regression-output.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-regression-output.__metaclass__.pbtxt index dbf4e3dec8..dcd7cbf427 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-regression-output.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.-regression-output.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.RegressionOutput.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.pbtxt index bd72f6cd79..8df585a5d9 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.estimator.export.pbtxt @@ -2,19 +2,19 @@ path: "tensorflow.estimator.export" tf_module { member { name: "ClassificationOutput" - mtype: "" + mtype: "" } member { name: "ExportOutput" - mtype: "" + mtype: "" } member { name: "PredictOutput" - mtype: "" + mtype: "" } member { name: "RegressionOutput" - mtype: "" + mtype: "" } member { name: "ServingInputReceiver" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-block-diag.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-block-diag.__metaclass__.pbtxt index b6dee63176..b1bed0c6db 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-block-diag.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-block-diag.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorBlockDiag.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant.__metaclass__.pbtxt index 3b33f3da97..5266853d48 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorCirculant.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant2-d.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant2-d.__metaclass__.pbtxt index 591bc9631a..515714fb57 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant2-d.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant2-d.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorCirculant2D.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant3-d.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant3-d.__metaclass__.pbtxt index d643139a53..6d2606ccb2 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant3-d.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-circulant3-d.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorCirculant3D.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-composition.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-composition.__metaclass__.pbtxt index 1adbcb41ad..09c61d4cb4 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-composition.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-composition.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorComposition.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-diag.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-diag.__metaclass__.pbtxt index 023d90ccdb..d13f7a1e44 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-diag.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-diag.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorDiag.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-full-matrix.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-full-matrix.__metaclass__.pbtxt index 381072e76c..f8fbfac13c 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-full-matrix.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-full-matrix.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorFullMatrix.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-identity.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-identity.__metaclass__.pbtxt index 5d115b35fb..d87f5d31d3 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-identity.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-identity.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorIdentity.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-kronecker.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-kronecker.__metaclass__.pbtxt index 5c6784dd02..d721caca39 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-kronecker.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-kronecker.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorKronecker.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-low-rank-update.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-low-rank-update.__metaclass__.pbtxt index 1f0d33298a..338f873788 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-low-rank-update.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-low-rank-update.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorLowRankUpdate.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-lower-triangular.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-lower-triangular.__metaclass__.pbtxt index 2683430f4f..4635320038 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-lower-triangular.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-lower-triangular.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorLowerTriangular.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-scaled-identity.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-scaled-identity.__metaclass__.pbtxt index 38bf7ad586..f3f370b35f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-scaled-identity.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-scaled-identity.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorScaledIdentity.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-zeros.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-zeros.__metaclass__.pbtxt index 49ff85728f..14dd9423e6 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-zeros.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-zeros.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorZeros.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator.__metaclass__.pbtxt index 38da809b36..dd5e383b5f 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperator.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt index 6ac95d96da..d584bf4932 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt @@ -2,59 +2,59 @@ path: "tensorflow.linalg" tf_module { member { name: "LinearOperator" - mtype: "" + mtype: "" } member { name: "LinearOperatorBlockDiag" - mtype: "" + mtype: "" } member { name: "LinearOperatorCirculant" - mtype: "" + mtype: "" } member { name: "LinearOperatorCirculant2D" - mtype: "" + mtype: "" } member { name: "LinearOperatorCirculant3D" - mtype: "" + mtype: "" } member { name: "LinearOperatorComposition" - mtype: "" + mtype: "" } member { name: "LinearOperatorDiag" - mtype: "" + mtype: "" } member { name: "LinearOperatorFullMatrix" - mtype: "" + mtype: "" } member { name: "LinearOperatorIdentity" - mtype: "" + mtype: "" } member { name: "LinearOperatorKronecker" - mtype: "" + mtype: "" } member { name: "LinearOperatorLowRankUpdate" - mtype: "" + mtype: "" } member { name: "LinearOperatorLowerTriangular" - mtype: "" + mtype: "" } member { name: "LinearOperatorScaledIdentity" - mtype: "" + mtype: "" } member { name: "LinearOperatorZeros" - mtype: "" + mtype: "" } member_method { name: "adjoint" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.train.-nan-loss-during-training-error.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.train.-nan-loss-during-training-error.pbtxt index 25fd5e75a7..e415819b3d 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.train.-nan-loss-during-training-error.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.train.-nan-loss-during-training-error.pbtxt @@ -6,10 +6,6 @@ tf_class { name: "args" mtype: "" } - member { - name: "message" - mtype: "" - } member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.-dataset.__metaclass__.pbtxt index af08c88d33..d81a3d986d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.Dataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.-fixed-length-record-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.-fixed-length-record-dataset.__metaclass__.pbtxt index f384323fc8..eb7c8dc264 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.-fixed-length-record-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.-fixed-length-record-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.FixedLengthRecordDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.-t-f-record-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.-t-f-record-dataset.__metaclass__.pbtxt index b12dec8a70..7cd273b2dd 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.-t-f-record-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.-t-f-record-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.TFRecordDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.-text-line-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.-text-line-dataset.__metaclass__.pbtxt index 7ddcdce266..b30f93ef5d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.-text-line-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.-text-line-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.TextLineDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-csv-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-csv-dataset.__metaclass__.pbtxt index 3eeaa1b185..604a1dc89e 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-csv-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-csv-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.experimental.CsvDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-random-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-random-dataset.__metaclass__.pbtxt index 2991b12f64..0c2300a4da 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-random-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-random-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.experimental.RandomDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-sql-dataset.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-sql-dataset.__metaclass__.pbtxt index 948e99ef86..f1a96b03e5 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-sql-dataset.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.-sql-dataset.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.data.experimental.SqlDataset.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt index f5f96ab98b..116684e5d8 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.experimental.pbtxt @@ -10,7 +10,7 @@ tf_module { } member { name: "CsvDataset" - mtype: "" + mtype: "" } member { name: "Optional" @@ -18,7 +18,7 @@ tf_module { } member { name: "RandomDataset" - mtype: "" + mtype: "" } member { name: "Reducer" @@ -26,7 +26,7 @@ tf_module { } member { name: "SqlDataset" - mtype: "" + mtype: "" } member { name: "StatsAggregator" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.data.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.data.pbtxt index 3023276a1d..509bbae833 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.data.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.data.pbtxt @@ -2,11 +2,11 @@ path: "tensorflow.data" tf_module { member { name: "Dataset" - mtype: "" + mtype: "" } member { name: "FixedLengthRecordDataset" - mtype: "" + mtype: "" } member { name: "Iterator" @@ -18,11 +18,11 @@ tf_module { } member { name: "TFRecordDataset" - mtype: "" + mtype: "" } member { name: "TextLineDataset" - mtype: "" + mtype: "" } member { name: "experimental" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-classification-output.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-classification-output.__metaclass__.pbtxt index 3cf7af8da9..820afac8e1 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-classification-output.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-classification-output.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.ClassificationOutput.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-export-output.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-export-output.__metaclass__.pbtxt index 5d165ccbf9..b811e1f3da 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-export-output.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-export-output.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.ExportOutput.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-predict-output.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-predict-output.__metaclass__.pbtxt index 743495ba98..bdfcb9c888 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-predict-output.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-predict-output.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.PredictOutput.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-regression-output.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-regression-output.__metaclass__.pbtxt index dbf4e3dec8..dcd7cbf427 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-regression-output.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.-regression-output.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.estimator.export.RegressionOutput.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.pbtxt index bd72f6cd79..8df585a5d9 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.estimator.export.pbtxt @@ -2,19 +2,19 @@ path: "tensorflow.estimator.export" tf_module { member { name: "ClassificationOutput" - mtype: "" + mtype: "" } member { name: "ExportOutput" - mtype: "" + mtype: "" } member { name: "PredictOutput" - mtype: "" + mtype: "" } member { name: "RegressionOutput" - mtype: "" + mtype: "" } member { name: "ServingInputReceiver" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-block-diag.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-block-diag.__metaclass__.pbtxt index b6dee63176..b1bed0c6db 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-block-diag.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-block-diag.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorBlockDiag.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant.__metaclass__.pbtxt index 3b33f3da97..5266853d48 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorCirculant.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant2-d.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant2-d.__metaclass__.pbtxt index 591bc9631a..515714fb57 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant2-d.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant2-d.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorCirculant2D.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant3-d.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant3-d.__metaclass__.pbtxt index d643139a53..6d2606ccb2 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant3-d.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-circulant3-d.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorCirculant3D.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-composition.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-composition.__metaclass__.pbtxt index 1adbcb41ad..09c61d4cb4 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-composition.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-composition.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorComposition.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-diag.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-diag.__metaclass__.pbtxt index 023d90ccdb..d13f7a1e44 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-diag.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-diag.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorDiag.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-full-matrix.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-full-matrix.__metaclass__.pbtxt index 381072e76c..f8fbfac13c 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-full-matrix.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-full-matrix.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorFullMatrix.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-identity.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-identity.__metaclass__.pbtxt index 5d115b35fb..d87f5d31d3 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-identity.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-identity.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorIdentity.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-kronecker.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-kronecker.__metaclass__.pbtxt index 5c6784dd02..d721caca39 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-kronecker.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-kronecker.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorKronecker.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-low-rank-update.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-low-rank-update.__metaclass__.pbtxt index 1f0d33298a..338f873788 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-low-rank-update.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-low-rank-update.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorLowRankUpdate.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-lower-triangular.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-lower-triangular.__metaclass__.pbtxt index 2683430f4f..4635320038 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-lower-triangular.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-lower-triangular.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorLowerTriangular.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-scaled-identity.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-scaled-identity.__metaclass__.pbtxt index 38bf7ad586..f3f370b35f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-scaled-identity.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-scaled-identity.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorScaledIdentity.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-zeros.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-zeros.__metaclass__.pbtxt index 49ff85728f..14dd9423e6 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-zeros.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-zeros.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperatorZeros.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator.__metaclass__.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator.__metaclass__.pbtxt index 38da809b36..dd5e383b5f 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator.__metaclass__.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator.__metaclass__.pbtxt @@ -1,6 +1,6 @@ path: "tensorflow.linalg.LinearOperator.__metaclass__" tf_class { - is_instance: "" + is_instance: "" member_method { name: "__init__" } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt index 6ac95d96da..d584bf4932 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt @@ -2,59 +2,59 @@ path: "tensorflow.linalg" tf_module { member { name: "LinearOperator" - mtype: "" + mtype: "" } member { name: "LinearOperatorBlockDiag" - mtype: "" + mtype: "" } member { name: "LinearOperatorCirculant" - mtype: "" + mtype: "" } member { name: "LinearOperatorCirculant2D" - mtype: "" + mtype: "" } member { name: "LinearOperatorCirculant3D" - mtype: "" + mtype: "" } member { name: "LinearOperatorComposition" - mtype: "" + mtype: "" } member { name: "LinearOperatorDiag" - mtype: "" + mtype: "" } member { name: "LinearOperatorFullMatrix" - mtype: "" + mtype: "" } member { name: "LinearOperatorIdentity" - mtype: "" + mtype: "" } member { name: "LinearOperatorKronecker" - mtype: "" + mtype: "" } member { name: "LinearOperatorLowRankUpdate" - mtype: "" + mtype: "" } member { name: "LinearOperatorLowerTriangular" - mtype: "" + mtype: "" } member { name: "LinearOperatorScaledIdentity" - mtype: "" + mtype: "" } member { name: "LinearOperatorZeros" - mtype: "" + mtype: "" } member_method { name: "adjoint" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.train.-nan-loss-during-training-error.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.train.-nan-loss-during-training-error.pbtxt index 25fd5e75a7..e415819b3d 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.train.-nan-loss-during-training-error.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.train.-nan-loss-during-training-error.pbtxt @@ -6,10 +6,6 @@ tf_class { name: "args" mtype: "" } - member { - name: "message" - mtype: "" - } member_method { name: "__init__" } diff --git a/tensorflow/tools/api/lib/python_object_to_proto_visitor.py b/tensorflow/tools/api/lib/python_object_to_proto_visitor.py index bf67334836..c4856a11da 100644 --- a/tensorflow/tools/api/lib/python_object_to_proto_visitor.py +++ b/tensorflow/tools/api/lib/python_object_to_proto_visitor.py @@ -19,7 +19,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import re +import enum import sys from google.protobuf import message from tensorflow.python.platform import tf_logging as logging @@ -32,60 +32,44 @@ _CORNER_CASES = { '': {'tools': {}}, 'test.TestCase': {}, 'test.TestCase.failureException': {}, + 'train.NanLossDuringTrainingError': {'message': {}}, } # Python 2 vs. 3 differences if sys.version_info.major == 3: - _CODE_ATTR = '__code__' - _CLASS_TO_TYPE = {} + _NORMALIZE_TYPE = {} for t in ('property', 'object', 'getset_descriptor', 'int', 'str', 'type', 'tuple', 'module', 'collections.defaultdict', 'set', 'dict', 'NoneType', 'frozenset'): - _CLASS_TO_TYPE["" % t] = "" % t + _NORMALIZE_TYPE["" % t] = "" % t for e in 'Exception', 'RuntimeError': - _CLASS_TO_TYPE["" % e] = "" % e + _NORMALIZE_TYPE["" % e] = "" % e + _NORMALIZE_TYPE[""] = "" _NORMALIZE_ISINSTANCE = { - "": + "": # pylint: disable=line-too-long "", "": ""} - def _normalize_type(ty): - return _CLASS_TO_TYPE.get(ty, ty) - - def _normalize_isinstance(ty): - return _NORMALIZE_ISINSTANCE.get(ty, ty) - def _skip_member(cls, member): - if member == 'with_traceback': - return True - if (cls in ('VariableSynchronization', 'UnconnectedGradients', 'VariableAggregation') and - member in ('name', 'value')): - return True - - def normalize_proto(proto): - for kind in 'tf_module', 'tf_class': - if proto.HasField(kind): - for member in getattr(proto, kind).member: - if member.mtype == "": - member.mtype = "" - if proto.path == 'tensorflow.train.NanLossDuringTrainingError': - del proto.tf_class.member[1] + return (member == 'with_traceback' or + member in ('name', 'value') and isinstance(cls, type) and + issubclass(cls, enum.Enum)) else: - _CODE_ATTR = 'func_code' - - def _normalize_type(ty): - return ty - - def _normalize_isinstance(ty): - return ty + _NORMALIZE_TYPE = {"": ""} + _NORMALIZE_ISINSTANCE = {} def _skip_member(cls, member): return False - def normalize_proto(proto): - pass + +def _normalize_type(ty): + return _NORMALIZE_TYPE.get(ty, ty) + + +def _normalize_isinstance(ty): + return _NORMALIZE_ISINSTANCE.get(ty, ty) def _SanitizedArgSpec(obj): @@ -185,7 +169,7 @@ class PythonObjectToProtoVisitor(object): def _AddMember(member_name, member_obj, proto): """Add the child object to the object being constructed.""" _, member_obj = tf_decorator.unwrap(member_obj) - if _skip_member(parent.__name__, member_name): + if _skip_member(parent, member_name): return if member_name == '__init__' or not member_name.startswith('_'): if tf_inspect.isroutine(member_obj): @@ -194,7 +178,7 @@ class PythonObjectToProtoVisitor(object): # If member_obj is a python builtin, there is no way to get its # argspec, because it is implemented on the C side. It also has no # func_code. - if hasattr(member_obj, _CODE_ATTR): + if hasattr(member_obj, '__code__'): new_method.argspec = _SanitizedArgSpec(member_obj) else: new_member = proto.member.add() @@ -229,7 +213,8 @@ class PythonObjectToProtoVisitor(object): elif tf_inspect.isclass(parent): # Construct a class. class_obj = api_objects_pb2.TFAPIClass() - class_obj.is_instance.extend(_normalize_isinstance(i) for i in _SanitizedMRO(parent)) + class_obj.is_instance.extend( + _normalize_isinstance(i) for i in _SanitizedMRO(parent)) for name, child in children: if name in parent_corner_cases: # If we have an empty entry, skip this object. diff --git a/tensorflow/tools/api/tests/api_compatibility_test.py b/tensorflow/tools/api/tests/api_compatibility_test.py index f069ace5b1..d94e8abb9a 100644 --- a/tensorflow/tools/api/tests/api_compatibility_test.py +++ b/tensorflow/tools/api/tests/api_compatibility_test.py @@ -31,7 +31,6 @@ import argparse import os import re import sys -import unittest import tensorflow as tf from tensorflow._api import v2 as tf_v2 @@ -299,7 +298,6 @@ class ApiCompatibilityTest(test.TestCase): """Read a filename, create a protobuf from its contents.""" ret_val = api_objects_pb2.TFAPIObject() text_format.Merge(file_io.read_file_to_string(filename), ret_val) - python_object_to_proto_visitor.normalize_proto(ret_val) return ret_val golden_proto_dict = { -- GitLab From 7bcbcc1392516a2b2d7a7abae2ccce7091c8dae3 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 02:02:20 -0700 Subject: [PATCH 0191/1825] compat: Update forward compatibility horizon to 2018-10-17 PiperOrigin-RevId: 217456158 --- 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 dac46aaa7d..f2967f6d71 100644 --- a/tensorflow/python/compat/compat.py +++ b/tensorflow/python/compat/compat.py @@ -26,7 +26,7 @@ import datetime from tensorflow.python.util import tf_contextlib from tensorflow.python.util.tf_export import tf_export -_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2018, 10, 16) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2018, 10, 17) @tf_export("compat.forward_compatible") -- GitLab From e16181f0f423df1861351fc725095468f6bf600b Mon Sep 17 00:00:00 2001 From: Anton Dmitriev Date: Wed, 17 Oct 2018 12:36:16 +0300 Subject: [PATCH 0192/1825] Server Java API updates after review. --- tensorflow/c/c_api.cc | 25 +++--- tensorflow/c/c_api.h | 43 ++++++++-- tensorflow/c/c_api_internal.h | 6 +- tensorflow/java/BUILD | 1 + .../src/main/java/org/tensorflow/Server.java | 84 +++++++++++++++---- tensorflow/java/src/main/native/BUILD | 1 - tensorflow/java/src/main/native/server_jni.cc | 42 ++++++++-- 7 files changed, 155 insertions(+), 47 deletions(-) diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc index 6101c1b6af..0d71aa3e94 100644 --- a/tensorflow/c/c_api.cc +++ b/tensorflow/c/c_api.cc @@ -2806,40 +2806,37 @@ TF_Buffer* TF_GetRegisteredKernelsForOp(const char* name, TF_Status* status) { // TF_Server functions ---------------------------------------------- -TF_Server::TF_Server(tensorflow::ServerInterface* server) : server(server) {} +TF_Server::TF_Server(std::unique_ptr server) + : server(std::move(server)) {} TF_Server* TF_NewServer(const void* proto, size_t proto_len, TF_Status* status) { tensorflow::ServerDef server_def; if (!server_def.ParseFromArray(proto, static_cast(proto_len))) { - status->status = InvalidArgument("Unparseable ServerDef"); + status->status = InvalidArgument( + "Could not parse provided bytes into a ServerDef protocol buffer"); return nullptr; } - auto out_server = new std::unique_ptr(); - status->status = tensorflow::NewServer(server_def, out_server); + std::unique_ptr out_server; + status->status = tensorflow::NewServer(server_def, &out_server); if (!status->status.ok()) return nullptr; - return new TF_Server(out_server->release()); + return new TF_Server(std::move(out_server)); } -void TF_StartServer(TF_Server* server, TF_Status* status) { +void TF_ServerStart(TF_Server* server, TF_Status* status) { status->status = server->server->Start(); } -void TF_StopServer(TF_Server* server, TF_Status* status) { +void TF_ServerStop(TF_Server* server, TF_Status* status) { status->status = server->server->Stop(); } -void TF_JoinServer(TF_Server* server, TF_Status* status) { +void TF_ServerJoin(TF_Server* server, TF_Status* status) { status->status = server->server->Join(); } -void TF_DeleteServer(TF_Server* server) { - if (server != nullptr) { - if (server->server != nullptr) delete server->server; - delete server; - } -} +void TF_DeleteServer(TF_Server* server) { delete server; } } // end extern "C" diff --git a/tensorflow/c/c_api.h b/tensorflow/c/c_api.h index bb5741e73d..9fe06f56a6 100644 --- a/tensorflow/c/c_api.h +++ b/tensorflow/c/c_api.h @@ -1663,26 +1663,53 @@ TF_CAPI_EXPORT extern TF_Buffer* TF_GetRegisteredKernelsForOp( const char* name, TF_Status* status); // -------------------------------------------------------------------------- -// Server functionality. - -// Server. +// In-process TensorFlow server functionality, for use in distributed training. +// A Server instance encapsulates a set of devices and a Session target that +// can participate in distributed training. A server belongs to a cluster +// (specified by a ClusterSpec), and corresponds to a particular task in a +// named job. The server can communicate with any other server in the same +// cluster. + +// In-process TensorFlow server. typedef struct TF_Server TF_Server; -// Creates new server. +// Creates a new server. The returned TF_Server object can be started, stopped +// and joined using correspondent commands. After using TF_Server object should +// be deleted using the TF_DeleteServer command to free correspondent resources. +// +// Params: +// proto - Serialized ServerDef protocol buffer. +// proto_len - Length of the proto. +// status - Set to OK on success and an appropriate error on failure. TF_CAPI_EXPORT extern TF_Server* TF_NewServer(const void* proto, size_t proto_len, TF_Status* status); // Starts a server. -TF_CAPI_EXPORT extern void TF_StartServer(TF_Server* server, TF_Status* status); +// +// Params: +// server - TF_Server object to be started. +// status - Set to OK on success and an appropriate error on failure. +TF_CAPI_EXPORT extern void TF_ServerStart(TF_Server* server, TF_Status* status); // Stops a server. -TF_CAPI_EXPORT extern void TF_StopServer(TF_Server* server, TF_Status* status); +// +// Params: +// server - TF_Server object to be stopped. +// status - Set to OK on success and an appropriate error on failure. +TF_CAPI_EXPORT extern void TF_ServerStop(TF_Server* server, TF_Status* status); // Blocks until the server has shut down (currently blocks forever). -TF_CAPI_EXPORT extern void TF_JoinServer(TF_Server* server, TF_Status* status); +// +// Params: +// server - TF_Server object to be joined. +// status - Set to OK on success and an appropriate error on failure. +TF_CAPI_EXPORT extern void TF_ServerJoin(TF_Server* server, TF_Status* status); -// Destroy a server, frees memory. +// Destroy a server, frees memory. Server is expected to be stopped before. +// +// Params: +// server - TF_Server object to be deleted. TF_CAPI_EXPORT extern void TF_DeleteServer(TF_Server* server); #ifdef __cplusplus diff --git a/tensorflow/c/c_api_internal.h b/tensorflow/c/c_api_internal.h index 59c8a2b7c7..9bb6edacaa 100644 --- a/tensorflow/c/c_api_internal.h +++ b/tensorflow/c/c_api_internal.h @@ -28,6 +28,7 @@ limitations under the License. #include "tensorflow/core/framework/op_gen_lib.h" #endif #include "tensorflow/core/common_runtime/shape_refiner.h" +#include "tensorflow/core/distributed_runtime/server_lib.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" #include "tensorflow/core/graph/graph.h" @@ -37,7 +38,6 @@ limitations under the License. #include "tensorflow/core/platform/mutex.h" #include "tensorflow/core/platform/types.h" #include "tensorflow/core/public/session.h" -#include "tensorflow/core/distributed_runtime/server_lib.h" namespace tensorflow { class Device; @@ -181,9 +181,9 @@ struct TF_ApiDefMap { }; struct TF_Server { - TF_Server(tensorflow::ServerInterface* server); + TF_Server(std::unique_ptr server); - tensorflow::ServerInterface* server; + std::unique_ptr server; }; namespace tensorflow { diff --git a/tensorflow/java/BUILD b/tensorflow/java/BUILD index 9dce78b9a3..3f847c4c18 100644 --- a/tensorflow/java/BUILD +++ b/tensorflow/java/BUILD @@ -382,6 +382,7 @@ tf_cc_binary( linkstatic = 1, deps = [ "//tensorflow/java/src/main/native", + "//tensorflow/core/distributed_runtime/rpc:grpc_server_lib", LINKER_VERSION_SCRIPT, LINKER_EXPORTED_SYMBOLS, ], diff --git a/tensorflow/java/src/main/java/org/tensorflow/Server.java b/tensorflow/java/src/main/java/org/tensorflow/Server.java index 18ee99e00a..5a42077904 100644 --- a/tensorflow/java/src/main/java/org/tensorflow/Server.java +++ b/tensorflow/java/src/main/java/org/tensorflow/Server.java @@ -15,26 +15,52 @@ limitations under the License. package org.tensorflow; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * An in-process TensorFlow server, for use in distributed training. * - * A {@code tf.train.Server} instance encapsulates a set of devices and a - * {@code tf.Session} target that can participate in distributed training. A - * server belongs to a cluster (specified by a {@code tf.train.ClusterSpec}), - * and corresponds to a particular task in a named job. The server can - * communicate with any other server in the same cluster. + * A {@code Server} instance encapsulates a set of devices and a + * {@link org.tensorflow.Session} target that can participate in distributed + * training. A server belongs to a cluster (specified by a + * {@code ClusterSpec}), and corresponds to a particular task in a named job. + * The server can communicate with any other server in the same cluster. * - *

WARNING:A {@code Server} owns resources that must be + *

WARNING: A {@code Server} owns resources that must be * explicitly freed by invoking {@link #close()}. * *

Instances of a {@code Server} are thread-safe. + * + *

Using example: + *

+ * {@code
+ * ClusterDef clusterDef = ClusterDef.newBuilder()
+ *   .addJob(JobDef.newBuilder()
+ *   .setName("worker")
+ *   .putTasks(0, "localhost:4321")
+ *   .build()
+ * ).build();
+ *
+ * ServerDef serverDef = ServerDef.newBuilder()
+ *   .setCluster(clusterDef)
+ *   .setJobName("worker")
+ *   .setTaskIndex(0)
+ *   .setProtocol("grpc")
+ * .build();
+ *
+ * try (Server srv = new Server(serverDef.toByteArray())) {
+ *   srv.start();
+ *   srv.join();
+ * }
+ * }
+ * 
*/ public final class Server implements AutoCloseable { /** * Constructs a new instance of server. * - * @param config Server definition specified as a serialized + * @param serverDef Server definition specified as a serialized * ServerDef * protocol buffer. */ @@ -43,25 +69,49 @@ public final class Server implements AutoCloseable { } /** Starts this server. */ - public synchronized void start() { - start(nativeHandle); + public void start() { + lock.readLock().lock(); + try { + start(nativeHandle); + } + finally { + lock.readLock().unlock(); + } } /** Stops this server. */ - public synchronized void stop() { - stop(nativeHandle); + public void stop() { + lock.readLock().lock(); + try { + stop(nativeHandle); + } + finally { + lock.readLock().unlock(); + } } /** Blocks until the server has shut down (currently blocks forever). */ - public synchronized void join() { - join(nativeHandle); + public void join() { + lock.readLock().lock(); + try { + join(nativeHandle); + } + finally { + lock.readLock().unlock(); + } } + /** Stops server and frees resources. Server is expected to be stopped before. */ @Override public void close() { - delete(nativeHandle); - - nativeHandle = 0; + lock.writeLock().lock(); + try { + delete(nativeHandle); + nativeHandle = 0; + } + finally { + lock.writeLock().unlock(); + } } private static native long allocate(byte[] serverDef); @@ -74,6 +124,8 @@ public final class Server implements AutoCloseable { private static native void delete(long nativeHandle); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private long nativeHandle; static { diff --git a/tensorflow/java/src/main/native/BUILD b/tensorflow/java/src/main/native/BUILD index 530224aa94..49348daa94 100644 --- a/tensorflow/java/src/main/native/BUILD +++ b/tensorflow/java/src/main/native/BUILD @@ -43,7 +43,6 @@ tf_cuda_library( "//tensorflow/core:all_kernels", "//tensorflow/core:direct_session", "//tensorflow/core:ops", - "//tensorflow/core/distributed_runtime/rpc:grpc_server_lib", ], }), alwayslink = 1, diff --git a/tensorflow/java/src/main/native/server_jni.cc b/tensorflow/java/src/main/native/server_jni.cc index 7eca920230..f0d1d29b88 100644 --- a/tensorflow/java/src/main/native/server_jni.cc +++ b/tensorflow/java/src/main/native/server_jni.cc @@ -29,44 +29,76 @@ JNIEXPORT jlong JNICALL Java_org_tensorflow_Server_allocate( status); env->ReleaseByteArrayElements(server_def, server_def_ptr, JNI_ABORT); - throwExceptionIfNotOK(env, status); + bool ok = throwExceptionIfNotOK(env, status); + + TF_DeleteStatus(status); - return reinterpret_cast(server); + return ok ? reinterpret_cast(server) : 0; } JNIEXPORT void JNICALL Java_org_tensorflow_Server_start(JNIEnv* env, jclass clazz, jlong handle) { + if (handle == 0) { + throwException(env, kIllegalStateException, + "close() has been called on the Server"); + return; + } + TF_Status* status = TF_NewStatus(); TF_Server* server = reinterpret_cast(handle); - TF_StartServer(server, status); + TF_ServerStart(server, status); throwExceptionIfNotOK(env, status); + + TF_DeleteStatus(status); } JNIEXPORT void JNICALL Java_org_tensorflow_Server_stop(JNIEnv* env, jclass clazz, jlong handle) { + if (handle == 0) { + throwException(env, kIllegalStateException, + "close() has been called on the Server"); + return; + } + TF_Status* status = TF_NewStatus(); TF_Server* server = reinterpret_cast(handle); - TF_StopServer(server, status); + TF_ServerStop(server, status); throwExceptionIfNotOK(env, status); + + TF_DeleteStatus(status); } JNIEXPORT void JNICALL Java_org_tensorflow_Server_join(JNIEnv* env, jclass clazz, jlong handle) { + if (handle == 0) { + throwException(env, kIllegalStateException, + "close() has been called on the Server"); + return; + } + TF_Status* status = TF_NewStatus(); TF_Server* server = reinterpret_cast(handle); - TF_JoinServer(server, status); + TF_ServerJoin(server, status); throwExceptionIfNotOK(env, status); + + TF_DeleteStatus(status); } JNIEXPORT void JNICALL Java_org_tensorflow_Server_delete(JNIEnv* env, jclass clazz, jlong handle) { + if (handle == 0) { + throwException(env, kIllegalStateException, + "close() has been called on the Server"); + return; + } + TF_Server* server = reinterpret_cast(handle); TF_DeleteServer(server); -- GitLab From 807578d77cf1d3760ce1ef6082c95b3d8a22ef5d Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 17 Oct 2018 13:55:09 +0200 Subject: [PATCH 0193/1825] fixing syntax format --- .../training/slurm_cluster_resolver_test.py | 99 ++++--------------- 1 file changed, 18 insertions(+), 81 deletions(-) diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py index fe3a886cd4..0ce09e8fa8 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py @@ -18,7 +18,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import subprocess import os from tensorflow.contrib.cluster_resolver.python.training.slurm_cluster_resolver import SlurmClusterResolver @@ -57,22 +56,9 @@ class SlurmClusterResolverTest(test.TestCase): actual_cluster_spec = slurm_cluster_resolver.cluster_spec() expected_proto = """ - job { - name: "ps" - tasks { - value: "t02n13:8888" - } - } - job { - name: "worker" - tasks { - value: "t02n41:8888" - } - tasks { - key: 1 - value: "t02n43:8888" - } - } + job { name: 'ps' tasks { value: 't02n13:8888' } } + job { name: 'worker' tasks { key: 0 value: 't02n41:8888' } + tasks { key: 1 value: 't02n43:8888' } } """ self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) @@ -91,22 +77,9 @@ class SlurmClusterResolverTest(test.TestCase): actual_cluster_spec = slurm_cluster_resolver.cluster_spec() expected_proto = """ - job { - name: "ps" - tasks { - value: "t02n13:8888" - } - } - job { - name: "worker" - tasks { - value: "t02n41:8888" - } - tasks { - key: 1 - value: "t02n43:8888" - } - } + job { name: 'ps' tasks { value: 't02n13:8888' } } + job { name: 'worker' tasks { key: 0 value: 't02n41:8888' } + tasks { key: 1 value: 't02n43:8888' } } """ self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) @@ -126,31 +99,13 @@ class SlurmClusterResolverTest(test.TestCase): actual_cluster_spec = slurm_cluster_resolver.cluster_spec() expected_proto = """ - job { - name: "ps" - tasks { - value: "t02n13:8888" - } - } - job { - name: "worker" - tasks { - value: "t02n13:8889" - } - tasks { - key: 1 - value: "t02n41:8888" - } - tasks { - key: 2 - value: "t02n41:8889" - } - tasks { - key: 3 - value: "t02n43:8888" - } - } + job { name: 'ps' tasks { value: 't02n13:8888' } } + job { name: 'worker' tasks { key: 0 value: 't02n13:8889' } + tasks { key: 1 value: 't02n41:8888' } } + tasks { key: 2 value: 't02n41:8889' } } + tasks { key: 3 value: 't02n43:8888' } } """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) assert os.environ['CUDA_VISIBLE_DEVICES'] == '1' @@ -170,31 +125,13 @@ class SlurmClusterResolverTest(test.TestCase): actual_cluster_spec = slurm_cluster_resolver.cluster_spec() expected_proto = """ - job { - name: "ps" - tasks { - value: "t02n13:8888" - } - } - job { - name: "worker" - tasks { - value: "t02n13:8889" - } - tasks { - key: 1 - value: "t02n41:8888" - } - tasks { - key: 2 - value: "t02n41:8889" - } - tasks { - key: 3 - value: "t02n43:8888" - } - } + job { name: 'ps' tasks { value: 't02n13:8888' } } + job { name: 'worker' tasks { key: 0 value: 't02n13:8889' } + tasks { key: 1 value: 't02n41:8888' } } + tasks { key: 2 value: 't02n41:8889' } } + tasks { key: 3 value: 't02n43:8888' } } """ + self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) assert os.environ['CUDA_VISIBLE_DEVICES'] == '2,3' -- GitLab From d4d0663c342c8112c741f5a48792ead64edfb39a Mon Sep 17 00:00:00 2001 From: steven Date: Wed, 17 Oct 2018 13:59:57 +0200 Subject: [PATCH 0194/1825] fixed extra } --- .../python/training/slurm_cluster_resolver_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py index 0ce09e8fa8..69853c97fb 100644 --- a/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py +++ b/tensorflow/contrib/cluster_resolver/python/training/slurm_cluster_resolver_test.py @@ -101,8 +101,8 @@ class SlurmClusterResolverTest(test.TestCase): expected_proto = """ job { name: 'ps' tasks { value: 't02n13:8888' } } job { name: 'worker' tasks { key: 0 value: 't02n13:8889' } - tasks { key: 1 value: 't02n41:8888' } } - tasks { key: 2 value: 't02n41:8889' } } + tasks { key: 1 value: 't02n41:8888' } + tasks { key: 2 value: 't02n41:8889' } tasks { key: 3 value: 't02n43:8888' } } """ @@ -127,8 +127,8 @@ class SlurmClusterResolverTest(test.TestCase): expected_proto = """ job { name: 'ps' tasks { value: 't02n13:8888' } } job { name: 'worker' tasks { key: 0 value: 't02n13:8889' } - tasks { key: 1 value: 't02n41:8888' } } - tasks { key: 2 value: 't02n41:8889' } } + tasks { key: 1 value: 't02n41:8888' } + tasks { key: 2 value: 't02n41:8889' } tasks { key: 3 value: 't02n43:8888' } } """ -- GitLab From bdc38ce6d50974fc7eced6cfdfeead5bda4d792b Mon Sep 17 00:00:00 2001 From: margaretmz Date: Thu, 13 Sep 2018 06:16:56 -0700 Subject: [PATCH 0195/1825] Updates to DCGAN tutorial: - Added TOC - Added intro paragraph - Added explanations of code - Added learn more about GANs section - Added a GANs network architecture diagram --- .../examples/generative_examples/dcgan.ipynb | 695 +----------------- .../generative_examples/gans_diagram.png | Bin 0 -> 63265 bytes 2 files changed, 1 insertion(+), 694 deletions(-) create mode 100644 tensorflow/contrib/eager/python/examples/generative_examples/gans_diagram.png diff --git a/tensorflow/contrib/eager/python/examples/generative_examples/dcgan.ipynb b/tensorflow/contrib/eager/python/examples/generative_examples/dcgan.ipynb index 5621d6a358..89e61c6194 100644 --- a/tensorflow/contrib/eager/python/examples/generative_examples/dcgan.ipynb +++ b/tensorflow/contrib/eager/python/examples/generative_examples/dcgan.ipynb @@ -1,694 +1 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "0TD5ZrvEMbhZ" - }, - "source": [ - "##### Copyright 2018 The TensorFlow Authors.\n", - "\n", - "Licensed under the Apache License, Version 2.0 (the \"License\").\n", - "\n", - "# DCGAN: An example with tf.keras and eager\n", - "\n", - "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\u003ctd\u003e\n", - "\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/generative_examples/dcgan.ipynb\"\u003e\n", - " \u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e \n", - "\u003c/td\u003e\u003ctd\u003e\n", - "\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples/generative_examples/dcgan.ipynb\"\u003e\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\u003c/table\u003e" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "ITZuApL56Mny" - }, - "source": [ - "This notebook demonstrates how to generate images of handwritten digits using [tf.keras](https://www.tensorflow.org/programmers_guide/keras) and [eager execution](https://www.tensorflow.org/programmers_guide/eager). To do so, we use Deep Convolutional Generative Adverserial Networks ([DCGAN](https://arxiv.org/pdf/1511.06434.pdf)).\n", - "\n", - "This model takes about ~30 seconds per epoch (using tf.contrib.eager.defun to create graph functions) to train on a single Tesla K80 on Colab, as of July 2018.\n", - "\n", - "Below is the output generated after training the generator and discriminator models for 150 epochs.\n", - "\n", - "![sample output](https://tensorflow.org/images/gan/dcgan.gif)" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "u_2z-B3piVsw" - }, - "outputs": [], - "source": [ - "# to generate gifs\n", - "!pip install imageio" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "e1_Y75QXJS6h" - }, - "source": [ - "## Import TensorFlow and enable eager execution" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "YfIk2es3hJEd" - }, - "outputs": [], - "source": [ - "from __future__ import absolute_import, division, print_function\n", - "\n", - "# Import TensorFlow \u003e= 1.10 and enable eager execution\n", - "import tensorflow as tf\n", - "tf.enable_eager_execution()\n", - "\n", - "import os\n", - "import time\n", - "import numpy as np\n", - "import glob\n", - "import matplotlib.pyplot as plt\n", - "import PIL\n", - "import imageio\n", - "from IPython import display" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "iYn4MdZnKCey" - }, - "source": [ - "## Load the dataset\n", - "\n", - "We are going to use the MNIST dataset to train the generator and the discriminator. The generator will then generate handwritten digits." - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "a4fYMGxGhrna" - }, - "outputs": [], - "source": [ - "(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "NFC2ghIdiZYE" - }, - "outputs": [], - "source": [ - "train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')\n", - "# We are normalizing the images to the range of [-1, 1]\n", - "train_images = (train_images - 127.5) / 127.5" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "S4PIDhoDLbsZ" - }, - "outputs": [], - "source": [ - "BUFFER_SIZE = 60000\n", - "BATCH_SIZE = 256" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "PIGN6ouoQxt3" - }, - "source": [ - "## Use tf.data to create batches and shuffle the dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "-yKCCQOoJ7cn" - }, - "outputs": [], - "source": [ - "train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "THY-sZMiQ4UV" - }, - "source": [ - "## Write the generator and discriminator models\n", - "\n", - "* **Generator** \n", - " * It is responsible for **creating convincing images that are good enough to fool the discriminator**.\n", - " * It consists of Conv2DTranspose (Upsampling) layers. We start with a fully connected layer and upsample the image 2 times so as to reach the desired image size (mnist image size) which is (28, 28, 1). \n", - " * We use **leaky relu** activation except for the **last layer** which uses **tanh** activation.\n", - " \n", - "* **Discriminator**\n", - " * **The discriminator is responsible for classifying the fake images from the real images.**\n", - " * In other words, the discriminator is given generated images (from the generator) and the real MNIST images. The job of the discriminator is to classify these images into fake (generated) and real (MNIST images).\n", - " * **Basically the generator should be good enough to fool the discriminator that the generated images are real**." - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "VGLbvBEmjK0a" - }, - "outputs": [], - "source": [ - "class Generator(tf.keras.Model):\n", - " def __init__(self):\n", - " super(Generator, self).__init__()\n", - " self.fc1 = tf.keras.layers.Dense(7*7*64, use_bias=False)\n", - " self.batchnorm1 = tf.keras.layers.BatchNormalization()\n", - " \n", - " self.conv1 = tf.keras.layers.Conv2DTranspose(64, (5, 5), strides=(1, 1), padding='same', use_bias=False)\n", - " self.batchnorm2 = tf.keras.layers.BatchNormalization()\n", - " \n", - " self.conv2 = tf.keras.layers.Conv2DTranspose(32, (5, 5), strides=(2, 2), padding='same', use_bias=False)\n", - " self.batchnorm3 = tf.keras.layers.BatchNormalization()\n", - " \n", - " self.conv3 = tf.keras.layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False)\n", - "\n", - " def call(self, x, training=True):\n", - " x = self.fc1(x)\n", - " x = self.batchnorm1(x, training=training)\n", - " x = tf.nn.relu(x)\n", - "\n", - " x = tf.reshape(x, shape=(-1, 7, 7, 64))\n", - "\n", - " x = self.conv1(x)\n", - " x = self.batchnorm2(x, training=training)\n", - " x = tf.nn.relu(x)\n", - "\n", - " x = self.conv2(x)\n", - " x = self.batchnorm3(x, training=training)\n", - " x = tf.nn.relu(x)\n", - "\n", - " x = tf.nn.tanh(self.conv3(x)) \n", - " return x" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "bkOfJxk5j5Hi" - }, - "outputs": [], - "source": [ - "class Discriminator(tf.keras.Model):\n", - " def __init__(self):\n", - " super(Discriminator, self).__init__()\n", - " self.conv1 = tf.keras.layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same')\n", - " self.conv2 = tf.keras.layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same')\n", - " self.dropout = tf.keras.layers.Dropout(0.3)\n", - " self.flatten = tf.keras.layers.Flatten()\n", - " self.fc1 = tf.keras.layers.Dense(1)\n", - "\n", - " def call(self, x, training=True):\n", - " x = tf.nn.leaky_relu(self.conv1(x))\n", - " x = self.dropout(x, training=training)\n", - " x = tf.nn.leaky_relu(self.conv2(x))\n", - " x = self.dropout(x, training=training)\n", - " x = self.flatten(x)\n", - " x = self.fc1(x)\n", - " return x" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "gDkA05NE6QMs" - }, - "outputs": [], - "source": [ - "generator = Generator()\n", - "discriminator = Discriminator()" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "k1HpMSLImuRi" - }, - "outputs": [], - "source": [ - "# Defun gives 10 secs/epoch performance boost\n", - "generator.call = tf.contrib.eager.defun(generator.call)\n", - "discriminator.call = tf.contrib.eager.defun(discriminator.call)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "0FMYgY_mPfTi" - }, - "source": [ - "## Define the loss functions and the optimizer\n", - "\n", - "* **Discriminator loss**\n", - " * The discriminator loss function takes 2 inputs; **real images, generated images**\n", - " * real_loss is a sigmoid cross entropy loss of the **real images** and an **array of ones (since these are the real images)**\n", - " * generated_loss is a sigmoid cross entropy loss of the **generated images** and an **array of zeros (since these are the fake images)**\n", - " * Then the total_loss is the sum of real_loss and the generated_loss\n", - " \n", - "* **Generator loss**\n", - " * It is a sigmoid cross entropy loss of the generated images and an **array of ones**\n", - " \n", - "\n", - "* The discriminator and the generator optimizers are different since we will train them separately." - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "wkMNfBWlT-PV" - }, - "outputs": [], - "source": [ - "def discriminator_loss(real_output, generated_output):\n", - " # [1,1,...,1] with real output since it is true and we want\n", - " # our generated examples to look like it\n", - " real_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=tf.ones_like(real_output), logits=real_output)\n", - "\n", - " # [0,0,...,0] with generated images since they are fake\n", - " generated_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=tf.zeros_like(generated_output), logits=generated_output)\n", - "\n", - " total_loss = real_loss + generated_loss\n", - "\n", - " return total_loss" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "90BIcCKcDMxz" - }, - "outputs": [], - "source": [ - "def generator_loss(generated_output):\n", - " return tf.losses.sigmoid_cross_entropy(tf.ones_like(generated_output), generated_output)" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "iWCn_PVdEJZ7" - }, - "outputs": [], - "source": [ - "discriminator_optimizer = tf.train.AdamOptimizer(1e-4)\n", - "generator_optimizer = tf.train.AdamOptimizer(1e-4)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "mWtinsGDPJlV" - }, - "source": [ - "## Checkpoints (Object-based saving)" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "CA1w-7s2POEy" - }, - "outputs": [], - "source": [ - "checkpoint_dir = './training_checkpoints'\n", - "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n", - "checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,\n", - " discriminator_optimizer=discriminator_optimizer,\n", - " generator=generator,\n", - " discriminator=discriminator)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "Rw1fkAczTQYh" - }, - "source": [ - "## Training\n", - "\n", - "* We start by iterating over the dataset\n", - "* The generator is given **noise as an input** which when passed through the generator model will output a image looking like a handwritten digit\n", - "* The discriminator is given the **real MNIST images as well as the generated images (from the generator)**.\n", - "* Next, we calculate the generator and the discriminator loss.\n", - "* Then, we calculate the gradients of loss with respect to both the generator and the discriminator variables (inputs) and apply those to the optimizer.\n", - "\n", - "## Generate Images\n", - "\n", - "* After training, its time to generate some images!\n", - "* We start by creating noise array as an input to the generator\n", - "* The generator will then convert the noise into handwritten images.\n", - "* Last step is to plot the predictions and **voila!**" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "NS2GWywBbAWo" - }, - "outputs": [], - "source": [ - "EPOCHS = 150\n", - "noise_dim = 100\n", - "num_examples_to_generate = 16\n", - "\n", - "# keeping the random vector constant for generation (prediction) so\n", - "# it will be easier to see the improvement of the gan.\n", - "random_vector_for_generation = tf.random_normal([num_examples_to_generate,\n", - " noise_dim])" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "RmdVsmvhPxyy" - }, - "outputs": [], - "source": [ - "def generate_and_save_images(model, epoch, test_input):\n", - " # make sure the training parameter is set to False because we\n", - " # don't want to train the batchnorm layer when doing inference.\n", - " predictions = model(test_input, training=False)\n", - "\n", - " fig = plt.figure(figsize=(4,4))\n", - " \n", - " for i in range(predictions.shape[0]):\n", - " plt.subplot(4, 4, i+1)\n", - " plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')\n", - " plt.axis('off')\n", - " \n", - " plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "2M7LmLtGEMQJ" - }, - "outputs": [], - "source": [ - "def train(dataset, epochs, noise_dim): \n", - " for epoch in range(epochs):\n", - " start = time.time()\n", - " \n", - " for images in dataset:\n", - " # generating noise from a uniform distribution\n", - " noise = tf.random_normal([BATCH_SIZE, noise_dim])\n", - " \n", - " with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n", - " generated_images = generator(noise, training=True)\n", - " \n", - " real_output = discriminator(images, training=True)\n", - " generated_output = discriminator(generated_images, training=True)\n", - " \n", - " gen_loss = generator_loss(generated_output)\n", - " disc_loss = discriminator_loss(real_output, generated_output)\n", - " \n", - " gradients_of_generator = gen_tape.gradient(gen_loss, generator.variables)\n", - " gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.variables)\n", - " \n", - " generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.variables))\n", - " discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.variables))\n", - "\n", - " \n", - " if epoch % 1 == 0:\n", - " display.clear_output(wait=True)\n", - " generate_and_save_images(generator,\n", - " epoch + 1,\n", - " random_vector_for_generation)\n", - " \n", - " # saving (checkpoint) the model every 15 epochs\n", - " if (epoch + 1) % 15 == 0:\n", - " checkpoint.save(file_prefix = checkpoint_prefix)\n", - " \n", - " print ('Time taken for epoch {} is {} sec'.format(epoch + 1,\n", - " time.time()-start))\n", - " # generating after the final epoch\n", - " display.clear_output(wait=True)\n", - " generate_and_save_images(generator,\n", - " epochs,\n", - " random_vector_for_generation)" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "Ly3UN0SLLY2l" - }, - "outputs": [], - "source": [ - "train(train_dataset, EPOCHS, noise_dim)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "rfM4YcPVPkNO" - }, - "source": [ - "## Restore the latest checkpoint" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "XhXsd0srPo8c" - }, - "outputs": [], - "source": [ - "# restoring the latest checkpoint in checkpoint_dir\n", - "checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "P4M_vIbUi7c0" - }, - "source": [ - "## Display an image using the epoch number" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "WfO5wCdclHGL" - }, - "outputs": [], - "source": [ - "def display_image(epoch_no):\n", - " return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "5x3q9_Oe5q0A" - }, - "outputs": [], - "source": [ - "display_image(EPOCHS)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "NywiH3nL8guF" - }, - "source": [ - "## Generate a GIF of all the saved images." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "xmO0Dmu2WICn" - }, - "source": [ - "\u003c!-- TODO(markdaoust): Remove the hack when Ipython version is updated --\u003e\n" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "IGKQgENQ8lEI" - }, - "outputs": [], - "source": [ - "with imageio.get_writer('dcgan.gif', mode='I') as writer:\n", - " filenames = glob.glob('image*.png')\n", - " filenames = sorted(filenames)\n", - " last = -1\n", - " for i,filename in enumerate(filenames):\n", - " frame = 2*(i**0.5)\n", - " if round(frame) \u003e round(last):\n", - " last = frame\n", - " else:\n", - " continue\n", - " image = imageio.imread(filename)\n", - " writer.append_data(image)\n", - " image = imageio.imread(filename)\n", - " writer.append_data(image)\n", - " \n", - "# this is a hack to display the gif inside the notebook\n", - "os.system('cp dcgan.gif dcgan.gif.png')" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "uV0yiKpzNP1b" - }, - "outputs": [], - "source": [ - "display.Image(filename=\"dcgan.gif.png\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "6EEG-wePkmJQ" - }, - "source": [ - "To downlod the animation from Colab uncomment the code below:" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "4UJjSnIMOzOJ" - }, - "outputs": [], - "source": [ - "#from google.colab import files\n", - "#files.download('dcgan.gif')" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "collapsed_sections": [], - "name": "dcgan.ipynb", - "private_outputs": true, - "provenance": [ - { - "file_id": "1eb0NOTQapkYs3X0v-zL1x5_LFKgDISnp", - "timestamp": 1527173385672 - } - ], - "toc_visible": true, - "version": "0.3.2" - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} +{"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"dcgan.ipynb","version":"0.3.2","provenance":[{"file_id":"1eb0NOTQapkYs3X0v-zL1x5_LFKgDISnp","timestamp":1527173385672}],"collapsed_sections":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"accelerator":"TPU"},"cells":[{"metadata":{"id":"0TD5ZrvEMbhZ","colab_type":"text"},"cell_type":"markdown","source":["**Copyright 2018 The TensorFlow Authors**.\n","\n","Licensed under the Apache License, Version 2.0 (the \"License\").\n","\n","# Generating Handwritten Digits with DCGAN\n","\n","
\n","\n"," Run in Google Colab \n","\n","View source on GitHub
"]},{"metadata":{"id":"ITZuApL56Mny","colab_type":"text"},"cell_type":"markdown","source":["This tutorial demonstrates how to generate images of handwritten digits with **Deep Convolutional Generative Adverserial Networks** ([DCGAN](https://arxiv.org/pdf/1511.06434.pdf)). The code is written in [tf.keras](https://www.tensorflow.org/programmers_guide/keras) with [eager execution](https://www.tensorflow.org/programmers_guide/eager) enabled. "]},{"metadata":{"id":"x2McrO9bMyLN","colab_type":"toc"},"cell_type":"markdown","source":[">[Generating Handwritten Digits with DCGAN](#scrollTo=0TD5ZrvEMbhZ)\n","\n",">>[What are GANs?](#scrollTo=2MbKJY38Puy9)\n","\n",">>>[Import TensorFlow and enable eager execution](#scrollTo=e1_Y75QXJS6h)\n","\n",">>>[Load the dataset](#scrollTo=iYn4MdZnKCey)\n","\n",">>>[Use tf.data to create batches and shuffle the dataset](#scrollTo=PIGN6ouoQxt3)\n","\n",">>[Create the models](#scrollTo=THY-sZMiQ4UV)\n","\n",">>>[The Generator Model](#scrollTo=-tEyxE-GMC48)\n","\n",">>>[The Discriminator model](#scrollTo=D0IKnaCtg6WE)\n","\n",">>[Define the loss functions and the optimizer](#scrollTo=0FMYgY_mPfTi)\n","\n",">>>[Generator loss](#scrollTo=Jd-3GCUEiKtv)\n","\n",">>>[Discriminator loss](#scrollTo=PKY_iPSPNWoj)\n","\n",">>[Set up GANs for Training](#scrollTo=Rw1fkAczTQYh)\n","\n",">>[Train the GANs](#scrollTo=dZrd4CdjR-Fp)\n","\n",">>[Generated images](#scrollTo=P4M_vIbUi7c0)\n","\n",">>[Learn more about GANs](#scrollTo=k6qC-SbjK0yW)\n","\n"]},{"metadata":{"id":"2MbKJY38Puy9","colab_type":"text"},"cell_type":"markdown","source":["## What are GANs?\n","GANs standards for **Generative Adversarial Networks** and they are a type of deep **generative** models. MIT [Intro to Deep Learning](http://introtodeeplearning.com/) lecture on **Deep Generative Models** has a great intro to generative models as well as GANs. ([video](https://youtu.be/JVb54xhEw6Y) | [slides](http://introtodeeplearning.com/materials/2018_6S191_Lecture4.pdf)). We have included more learning resources on these subjects in the \"Learn more about GANs\" section at the end of the tutorial.\n","\n","Many deep learning models, for example using a CNN for classification, are based on optimization: finding the low value of the cost function. GANs are different because there are at least two players (or network models): a generator and a discriminator and each has its own cost. Training GANs is like a two-player game (**adversarial**) such as chess where each player plays against each other.\n","\n"," **Deep Convolutional GAN** (DCGAN) is a type of GANs and in this tutorial we will use DCGAN to generate MNIST digits.\n","\n","GANs can be used to generate new images that no one has seen before. The generator will generate fake images while the discriminator will classify whether the generated images are fake. An** equilibrium** will reach in the game when the generator makes data that looks identical to the training data and the discriminator can no longer tell the difference between the fake images (generated by the generator) and the real images (the training data). \n","\n","![alt text](https://github.com/margaretmz/tensorflow/blob/margaret-dcgan/tensorflow/contrib/eager/python/examples/generative_examples/gans_diagram.png?raw=1)\n","\n","While the generator and discriminator competes against each other, the discriminator also teaches the generator . Over time the generator starts to produce images that resemble the training data that is fed into the discriminator, in this case the MNIST digits. Below is the output with images generated after training the generator and discriminator models for 150 epochs.\n","\n","![sample output](https://tensorflow.org/images/gan/dcgan.gif)"]},{"metadata":{"id":"39wxvRihPvW3","colab_type":"text"},"cell_type":"markdown","source":["Installation, Imports and prepare the datasets"]},{"metadata":{"id":"u_2z-B3piVsw","colab_type":"code","colab":{"base_uri":"https://localhost:8080/","height":221},"outputId":"684f2b6e-7756-448e-da2a-74bcb08d8686","executionInfo":{"status":"ok","timestamp":1539403781878,"user_tz":420,"elapsed":10403,"user":{"displayName":"Margaret Maynard-Reid","photoUrl":"","userId":"16644161164743621476"}}},"cell_type":"code","source":["# install imgeio in order to generate an animated gif showing the image generating process\n","!pip install imageio"],"execution_count":1,"outputs":[{"output_type":"stream","text":["Collecting imageio\n","\u001b[?25l Downloading https://files.pythonhosted.org/packages/28/b4/cbb592964dfd71a9de6a5b08f882fd334fb99ae09ddc82081dbb2f718c81/imageio-2.4.1.tar.gz (3.3MB)\n","\u001b[K 100% |████████████████████████████████| 3.3MB 5.5MB/s \n","\u001b[?25hRequirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from imageio) (1.14.6)\n","Requirement already satisfied: pillow in /usr/local/lib/python3.6/dist-packages (from imageio) (4.0.0)\n","Requirement already satisfied: olefile in /usr/local/lib/python3.6/dist-packages (from pillow->imageio) (0.46)\n","Building wheels for collected packages: imageio\n"," Running setup.py bdist_wheel for imageio ... \u001b[?25l-\b \b\\\b \b|\b \bdone\n","\u001b[?25h Stored in directory: /root/.cache/pip/wheels/e0/43/31/605de9372ceaf657f152d3d5e82f42cf265d81db8bbe63cde1\n","Successfully built imageio\n","Installing collected packages: imageio\n","Successfully installed imageio-2.4.1\n"],"name":"stdout"}]},{"metadata":{"id":"e1_Y75QXJS6h","colab_type":"text"},"cell_type":"markdown","source":["### Import TensorFlow and enable eager execution\n","\n","Note: you can only call tf.enable_eager_execution once. \n","Restart runtime in colab and rerun the cells if you get an error as below:\n","\n","*ValueError: tf.enable_eager_execution must be called at program startup.*"]},{"metadata":{"id":"YfIk2es3hJEd","colab_type":"code","colab":{}},"cell_type":"code","source":["from __future__ import absolute_import, division, print_function\n","\n","# Import TensorFlow >= 1.10 and enable eager execution\n","import tensorflow as tf\n","tf.enable_eager_execution()\n","\n","import os\n","import time\n","import numpy as np\n","import glob\n","import matplotlib.pyplot as plt\n","import PIL\n","import imageio\n","from IPython import display"],"execution_count":0,"outputs":[]},{"metadata":{"id":"iYn4MdZnKCey","colab_type":"text"},"cell_type":"markdown","source":["### Load the dataset\n","\n","We are going to use the MNIST dataset to train the generator and the discriminator. The generator will generate handwritten digits resembling the MNIST data."]},{"metadata":{"id":"a4fYMGxGhrna","colab_type":"code","colab":{"base_uri":"https://localhost:8080/","height":51},"outputId":"065f5f41-bdd6-4f4e-bdb6-addce8ff011d","executionInfo":{"status":"ok","timestamp":1539403786062,"user_tz":420,"elapsed":1339,"user":{"displayName":"Margaret Maynard-Reid","photoUrl":"","userId":"16644161164743621476"}}},"cell_type":"code","source":["(train_images, train_labels), (_, _) = tf.keras.datasets.mnist.load_data()"],"execution_count":3,"outputs":[{"output_type":"stream","text":["Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz\n","11493376/11490434 [==============================] - 0s 0us/step\n"],"name":"stdout"}]},{"metadata":{"id":"NFC2ghIdiZYE","colab_type":"code","colab":{}},"cell_type":"code","source":["train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')\n","# We are normalizing the images to the range of [-1, 1]\n","train_images = (train_images - 127.5) / 127.5"],"execution_count":0,"outputs":[]},{"metadata":{"id":"S4PIDhoDLbsZ","colab_type":"code","colab":{}},"cell_type":"code","source":["BUFFER_SIZE = 60000\n","BATCH_SIZE = 256"],"execution_count":0,"outputs":[]},{"metadata":{"id":"PIGN6ouoQxt3","colab_type":"text"},"cell_type":"markdown","source":["### Use tf.data to create batches and shuffle the dataset"]},{"metadata":{"id":"-yKCCQOoJ7cn","colab_type":"code","colab":{}},"cell_type":"code","source":["train_dataset = tf.data.Dataset.from_tensor_slices(train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"THY-sZMiQ4UV","colab_type":"text"},"cell_type":"markdown","source":["## Create the models\n","\n","We will use tf.keras model subclassing to create the generator and discriminator. We will create layers in the __init__ method and set them as attributes of the class instance. And then define the forward pass in the **call **method."]},{"metadata":{"id":"-tEyxE-GMC48","colab_type":"text"},"cell_type":"markdown","source":["### The Generator Model\n","\n","The **generator **is responsible for **creating convincing images that are good enough to fool the discriminator**. \n","\n","Here is the network architecture for the generator:\n"," * It consists of Conv2DTranspose (Upsampling) layers. We start with a fully connected layer and **upsample** the image 2 times in order to reach the desired image size as mnist image size of (28, 28, 1). We increase the width and height, and reduce the depth as we move through the layers in the network.\n"," * We use **leaky relu** activation except for the **last layer** which uses **tanh** activation."]},{"metadata":{"id":"VGLbvBEmjK0a","colab_type":"code","colab":{}},"cell_type":"code","source":["class Generator(tf.keras.Model):\n"," def __init__(self):\n"," super(Generator, self).__init__()\n"," self.fc1 = tf.keras.layers.Dense(7*7*64, use_bias=False)\n"," self.batchnorm1 = tf.keras.layers.BatchNormalization()\n"," \n"," self.conv1 = tf.keras.layers.Conv2DTranspose(64, (5, 5), strides=(1, 1), padding='same', use_bias=False)\n"," # Layer shape is now 7x7x64 \n"," \n"," self.batchnorm2 = tf.keras.layers.BatchNormalization()\n","\n"," self.conv2 = tf.keras.layers.Conv2DTranspose(32, (5, 5), strides=(2, 2), padding='same', use_bias=False)\n"," # Layer shape is now 14x14x32\n"," \n"," self.batchnorm3 = tf.keras.layers.BatchNormalization()\n"," \n"," self.conv3 = tf.keras.layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False)\n"," # Layer shape is now 28x28x1\n","\n"," def call(self, x, training=True):\n"," x = self.fc1(x)\n"," x = self.batchnorm1(x, training=training)\n"," x = tf.nn.relu(x)\n","\n"," x = tf.reshape(x, shape=(-1, 7, 7, 64))\n","\n"," x = self.conv1(x)\n"," x = self.batchnorm2(x, training=training)\n"," x = tf.nn.relu(x)\n","\n"," x = self.conv2(x)\n"," x = self.batchnorm3(x, training=training)\n"," x = tf.nn.relu(x)\n","\n"," x = tf.nn.tanh(self.conv3(x)) \n"," return x"],"execution_count":0,"outputs":[]},{"metadata":{"id":"D0IKnaCtg6WE","colab_type":"text"},"cell_type":"markdown","source":["### The Discriminator model\n","\n","The **discriminator** is responsible for classifying the fake images from the real images. It's similar to a regular CNN image classifier.\n"," * **Input **to the discriminator: images generated by the generator and the real MNIST images. \n"," * **Output** from the discriminator: classify these images into fake (generated) and real (MNIST images).\n"]},{"metadata":{"id":"bkOfJxk5j5Hi","colab_type":"code","colab":{}},"cell_type":"code","source":["class Discriminator(tf.keras.Model):\n"," def __init__(self):\n"," super(Discriminator, self).__init__()\n"," self.conv1 = tf.keras.layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same')\n"," self.conv2 = tf.keras.layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same')\n"," self.dropout = tf.keras.layers.Dropout(0.3)\n"," self.flatten = tf.keras.layers.Flatten()\n"," self.fc1 = tf.keras.layers.Dense(1)\n","\n"," def call(self, x, training=True):\n"," x = tf.nn.leaky_relu(self.conv1(x))\n"," x = self.dropout(x, training=training)\n"," x = tf.nn.leaky_relu(self.conv2(x))\n"," x = self.dropout(x, training=training)\n"," x = self.flatten(x)\n"," x = self.fc1(x)\n"," return x"],"execution_count":0,"outputs":[]},{"metadata":{"id":"gDkA05NE6QMs","colab_type":"code","colab":{}},"cell_type":"code","source":["generator = Generator()\n","discriminator = Discriminator()"],"execution_count":0,"outputs":[]},{"metadata":{"id":"6TSZgwc2BUQ-","colab_type":"text"},"cell_type":"markdown","source":["\n","This model takes about ~30 seconds per epoch to train on a single Tesla K80 on Colab, as of July 2018. Eager execution can sometimes be slower than executing the equivalent graph due to overheads of interpreting Python code. By using [tf.contrib.eager.defun](https://www.tensorflow.org/api_docs/python/tf/contrib/eager/defun) to create graph functions, we get 10 secs/epoch performance boost. This way we get the best of both eager execution (easier for debugging) and graph mode (better performance)."]},{"metadata":{"id":"k1HpMSLImuRi","colab_type":"code","colab":{}},"cell_type":"code","source":["generator.call = tf.contrib.eager.defun(generator.call)\n","discriminator.call = tf.contrib.eager.defun(discriminator.call)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"0FMYgY_mPfTi","colab_type":"text"},"cell_type":"markdown","source":["## Define the loss functions and the optimizer\n","\n","Let's define the loss functions and the optimizers for the generator and the discriminator.\n"]},{"metadata":{"id":"Jd-3GCUEiKtv","colab_type":"text"},"cell_type":"markdown","source":["### Generator loss\n","The generator loss is a sigmoid cross entropy loss of the **generated images** and an **array of ones**, since the generator is trying to generate fake images that resemble the real images."]},{"metadata":{"id":"90BIcCKcDMxz","colab_type":"code","colab":{}},"cell_type":"code","source":["def generator_loss(generated_output):\n"," return tf.losses.sigmoid_cross_entropy(tf.ones_like(generated_output), generated_output)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"PKY_iPSPNWoj","colab_type":"text"},"cell_type":"markdown","source":["### Discriminator loss\n","\n","The discriminator loss function takes 2 inputs; **real images, generated images**.\n","\n","Here is how to calculate the discriminator loss:\n","1. Calculate real_loss which is a sigmoid cross entropy loss of the **real images** and an **array of ones (since these are the real images)**\n","2. Calculate generated_loss which is a sigmoid cross entropy loss of the **generated images** and an **array of zeros (since these are the fake images)**\n","3. Calculate the total_loss as **the sum of real_loss and generated_loss**"]},{"metadata":{"id":"wkMNfBWlT-PV","colab_type":"code","colab":{}},"cell_type":"code","source":["def discriminator_loss(real_output, generated_output):\n"," # [1,1,...,1] with real output since it is true and we want\n"," # our generated examples to look like it\n"," real_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=tf.ones_like(real_output), logits=real_output)\n","\n"," # [0,0,...,0] with generated images since they are fake\n"," generated_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=tf.zeros_like(generated_output), logits=generated_output)\n","\n"," total_loss = real_loss + generated_loss\n","\n"," return total_loss"],"execution_count":0,"outputs":[]},{"metadata":{"id":"MgIc7i0th_Iu","colab_type":"text"},"cell_type":"markdown","source":["The discriminator and the generator optimizers are different since we will train two networks separately."]},{"metadata":{"id":"iWCn_PVdEJZ7","colab_type":"code","colab":{}},"cell_type":"code","source":["generator_optimizer = tf.train.AdamOptimizer(1e-4)\n","discriminator_optimizer = tf.train.AdamOptimizer(1e-4)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"mWtinsGDPJlV","colab_type":"text"},"cell_type":"markdown","source":["**Checkpoints (Object-based saving)**"]},{"metadata":{"id":"CA1w-7s2POEy","colab_type":"code","colab":{}},"cell_type":"code","source":["checkpoint_dir = './training_checkpoints'\n","checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n","checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,\n"," discriminator_optimizer=discriminator_optimizer,\n"," generator=generator,\n"," discriminator=discriminator)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"Rw1fkAczTQYh","colab_type":"text"},"cell_type":"markdown","source":["## Set up GANs for Training\n","\n"]},{"metadata":{"id":"5QC5BABamh_c","colab_type":"text"},"cell_type":"markdown","source":["Now it's time to put together the generator and discriminator to set up the Generative Adversarial Networks, as you wee the diagam in the beginning of the tutorial."]},{"metadata":{"id":"Ff6oN6PZX27n","colab_type":"text"},"cell_type":"markdown","source":["**Define training parameters**"]},{"metadata":{"id":"NS2GWywBbAWo","colab_type":"code","colab":{}},"cell_type":"code","source":["EPOCHS = 150\n","noise_dim = 100\n","num_examples_to_generate = 16\n","\n","# keeping the random vector constant for generation (prediction) so\n","# it will be easier to see the improvement of the gan.\n","random_vector_for_generation = tf.random_normal([num_examples_to_generate,\n"," noise_dim])"],"execution_count":0,"outputs":[]},{"metadata":{"id":"jylSonrqSWfi","colab_type":"text"},"cell_type":"markdown","source":["**Define training method**\n","\n","We start by iterating over the dataset. The generator is given **noise as an input** which is passed through the generator model and output a image looking like a handwritten digit. The discriminator is given the **real MNIST images as well as the generated images (from the generator)**.\n","\n","Next, we calculate the generator and the discriminator loss. Then we calculate the gradients of loss with respect to both the generator and the discriminator variables (inputs) and apply those to the optimizer."]},{"metadata":{"id":"2M7LmLtGEMQJ","colab_type":"code","colab":{}},"cell_type":"code","source":["def train(dataset, epochs, noise_dim): \n"," for epoch in range(epochs):\n"," start = time.time()\n"," \n"," for images in dataset:\n"," # generating noise from a uniform distribution\n"," noise = tf.random_normal([BATCH_SIZE, noise_dim])\n"," \n"," with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n"," generated_images = generator(noise, training=True)\n"," \n"," real_output = discriminator(images, training=True)\n"," generated_output = discriminator(generated_images, training=True)\n"," \n"," gen_loss = generator_loss(generated_output)\n"," disc_loss = discriminator_loss(real_output, generated_output)\n"," \n"," gradients_of_generator = gen_tape.gradient(gen_loss, generator.variables)\n"," gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.variables)\n"," \n"," generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.variables))\n"," discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.variables))\n","\n"," \n"," if epoch % 1 == 0:\n"," display.clear_output(wait=True)\n"," generate_and_save_images(generator,\n"," epoch + 1,\n"," random_vector_for_generation)\n"," \n"," # saving (checkpoint) the model every 15 epochs\n"," if (epoch + 1) % 15 == 0:\n"," checkpoint.save(file_prefix = checkpoint_prefix)\n"," \n"," print ('Time taken for epoch {} is {} sec'.format(epoch + 1,\n"," time.time()-start))\n"," # generating after the final epoch\n"," display.clear_output(wait=True)\n"," generate_and_save_images(generator,\n"," epochs,\n"," random_vector_for_generation)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"2aFF7Hk3XdeW","colab_type":"text"},"cell_type":"markdown","source":["**Generate and save images**\n","\n"]},{"metadata":{"id":"RmdVsmvhPxyy","colab_type":"code","colab":{}},"cell_type":"code","source":["def generate_and_save_images(model, epoch, test_input):\n"," # make sure the training parameter is set to False because we\n"," # don't want to train the batchnorm layer when doing inference.\n"," predictions = model(test_input, training=False)\n","\n"," fig = plt.figure(figsize=(4,4))\n"," \n"," for i in range(predictions.shape[0]):\n"," plt.subplot(4, 4, i+1)\n"," plt.imshow(predictions[i, :, :, 0] * 127.5 + 127.5, cmap='gray')\n"," plt.axis('off')\n"," \n"," plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))\n"," plt.show()"],"execution_count":0,"outputs":[]},{"metadata":{"id":"dZrd4CdjR-Fp","colab_type":"text"},"cell_type":"markdown","source":["## Train the GANs\n","We will call the train() method defined above to train the generator and discriminator simultaneously. Note training GANs can be tricky and it's important that the generator and discriminator are not overpowering each other so that the generator is able able to generate while the discriminator is able to discriminate.\n","\n","At the beginning of the training, the images generated look more like the input random noise. As the training goes on, you can see the digits generated are looking better. After 150 epochs they look very much like the MNIST digits."]},{"metadata":{"id":"Ly3UN0SLLY2l","colab_type":"code","colab":{}},"cell_type":"code","source":["%%time\n","train(train_dataset, EPOCHS, noise_dim)"],"execution_count":0,"outputs":[]},{"metadata":{"id":"rfM4YcPVPkNO","colab_type":"text"},"cell_type":"markdown","source":["**Restore the latest checkpoint**"]},{"metadata":{"id":"XhXsd0srPo8c","colab_type":"code","colab":{"base_uri":"https://localhost:8080/","height":34},"outputId":"8571b12f-f4b6-422b-8b2e-c8f22e9d7e2d","executionInfo":{"status":"ok","timestamp":1537658569893,"user_tz":420,"elapsed":1594,"user":{"displayName":"Margaret Maynard-Reid","photoUrl":"//lh4.googleusercontent.com/-CaD6Qnc1cqA/AAAAAAAAAAI/AAAAAAACgho/cBw_luxyXso/s50-c-k-no/photo.jpg","userId":"103983505199499372479"}}},"cell_type":"code","source":["# restoring the latest checkpoint in checkpoint_dir\n","checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))"],"execution_count":19,"outputs":[{"output_type":"execute_result","data":{"text/plain":[""]},"metadata":{"tags":[]},"execution_count":19}]},{"metadata":{"id":"P4M_vIbUi7c0","colab_type":"text"},"cell_type":"markdown","source":["## Generated images \n"]},{"metadata":{"id":"mLskt7EfXAjr","colab_type":"text"},"cell_type":"markdown","source":["\n","After training, its time to generate some images! \n","The last step is to plot the generated images and **voila!**\n"]},{"metadata":{"id":"WfO5wCdclHGL","colab_type":"code","colab":{}},"cell_type":"code","source":["# Display a single image using the epoch number\n","def display_image(epoch_no):\n"," return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))"],"execution_count":0,"outputs":[]},{"metadata":{"id":"5x3q9_Oe5q0A","colab_type":"code","colab":{"base_uri":"https://localhost:8080/","height":305},"outputId":"38908d9f-d1f3-42c2-c552-f3efebd58a11","executionInfo":{"status":"ok","timestamp":1537658573171,"user_tz":420,"elapsed":1684,"user":{"displayName":"Margaret Maynard-Reid","photoUrl":"//lh4.googleusercontent.com/-CaD6Qnc1cqA/AAAAAAAAAAI/AAAAAAACgho/cBw_luxyXso/s50-c-k-no/photo.jpg","userId":"103983505199499372479"}}},"cell_type":"code","source":["display_image(EPOCHS)"],"execution_count":21,"outputs":[{"output_type":"execute_result","data":{"image/png":"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\nAElFTkSuQmCC\n","text/plain":[""]},"metadata":{"tags":[]},"execution_count":21}]},{"metadata":{"id":"NywiH3nL8guF","colab_type":"text"},"cell_type":"markdown","source":["**Generate a GIF of all the saved images**\n","\n","We will use imageio to create an animated gif using all the images saved during training."]},{"metadata":{"id":"IGKQgENQ8lEI","colab_type":"code","colab":{"base_uri":"https://localhost:8080/","height":34},"outputId":"bf66aad8-fbe4-4b1f-c260-bccf9c634867","executionInfo":{"status":"ok","timestamp":1537658575025,"user_tz":420,"elapsed":1604,"user":{"displayName":"Margaret Maynard-Reid","photoUrl":"//lh4.googleusercontent.com/-CaD6Qnc1cqA/AAAAAAAAAAI/AAAAAAACgho/cBw_luxyXso/s50-c-k-no/photo.jpg","userId":"103983505199499372479"}}},"cell_type":"code","source":["with imageio.get_writer('dcgan.gif', mode='I') as writer:\n"," filenames = glob.glob('image*.png')\n"," filenames = sorted(filenames)\n"," last = -1\n"," for i,filename in enumerate(filenames):\n"," frame = 2*(i**0.5)\n"," if round(frame) > round(last):\n"," last = frame\n"," else:\n"," continue\n"," image = imageio.imread(filename)\n"," writer.append_data(image)\n"," image = imageio.imread(filename)\n"," writer.append_data(image)\n"," \n","# this is a hack to display the gif inside the notebook\n","os.system('cp dcgan.gif dcgan.gif.png')"],"execution_count":22,"outputs":[{"output_type":"execute_result","data":{"text/plain":["0"]},"metadata":{"tags":[]},"execution_count":22}]},{"metadata":{"id":"cGhC3-fMWSwl","colab_type":"text"},"cell_type":"markdown","source":["Display the animated gif with all the mages generated during the training of GANs."]},{"metadata":{"id":"uV0yiKpzNP1b","colab_type":"code","colab":{"base_uri":"https://localhost:8080/","height":305},"outputId":"a6146795-f0ae-4746-bbd3-5e19155e2c77","executionInfo":{"status":"ok","timestamp":1537658577831,"user_tz":420,"elapsed":2555,"user":{"displayName":"Margaret Maynard-Reid","photoUrl":"//lh4.googleusercontent.com/-CaD6Qnc1cqA/AAAAAAAAAAI/AAAAAAACgho/cBw_luxyXso/s50-c-k-no/photo.jpg","userId":"103983505199499372479"}}},"cell_type":"code","source":["display.Image(filename=\"dcgan.gif.png\")"],"execution_count":23,"outputs":[{"output_type":"execute_result","data":{"image/png":"R0lGODlhIAEgAYcAAP////7+/v39/fz8/Pv7+/r6+vn5+fj4+Pf39/b29vX19fPz8/Ly8vHx8fDw\n8O/v7+7u7u3t7ezs7Ovr6+rq6unp6ejo6Ofn5+bm5uXl5ePj4+Li4uHh4eDg4N/f397e3t3d3dzc\n3Nvb29ra2tnZ2djY2NfX19bW1tXV1dPT09LS0tHR0dDQ0M/Pz87Ozs3NzczMzMvLy8rKysnJycjI\nyMfHx8bGxsXFxcPDw8LCwsHBwcDAwL+/v76+vr29vby8vLu7u7q6urm5ubi4uLe3t7a2trW1tbOz\ns7KysrGxsbCwsK+vr66urq2traysrKurq6qqqqmpqaioqKenp6ampqWlpaOjo6KioqGhoaCgoJ+f\nn56enp2dnZycnJubm5qampmZmZiYmJeXl5aWlpWVlZOTk5KSkpGRkZCQkI+Pj46Ojo2NjYyMjIuL\ni4qKiomJiYiIiIeHh4aGhoWFhYODg4KCgoGBgYCAgH9/f35+fn19fXx8fHt7e3l5eXh4eHd3d3Z2\ndnV1dXR0dHNzc3FxcXBwcG9vb25ubm1tbWxsbGtra2lpaWhoaGdnZ2ZmZmVlZWRkZGNjY2FhYWBg\nYF9fX15eXl1dXVxcXFtbW1lZWVhYWFdXV1ZWVlVVVVRUVFNTU1FRUVBQUE9PT05OTk1NTUxMTEtL\nS0lJSUhISEdHR0ZGRkVFRURERENDQ0FBQUBAQD8/Pz4+Pjw8PDs7Ozo6Ojg4ODc3NzY2NjQ0NDMz\nMzIyMjAwMC8vLy4uLiwsLCsrKyoqKigoKCcnJyYmJiQkJCMjIyIiIiAgIB8fHx4eHh0dHRwcHBsb\nGxoaGhkZGRgYGBcXFxYWFhUVFRQUFBMTExISEhERERAQEA8PDw4ODg0NDQwMDAsLCwoKCgkJCQgI\nCAcHBwYGBgUFBQQEBAMDAwICAgEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwH/\n/wAh+QQICgAAACwAAAAAIAEgAQAI/wABCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPH\njyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iTKl3KtKnTp1Cj\n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\nc+bOnRtXrty4ceXKZQMHbhvAbd2qVfv2jRu3cNmyiRMHbtw4btzChfN27RqAjBo3QoNGLly4c+fI\nkTtn0qQ4cuTEiRs37po3b9q0daNGDRy4b9/Gdes2bpy4cuW6dQsXDly2bACWMm3q9CnUqFKnUq1q\n9SrWrFq3cu3q9SvYsGLHki1r9izatGrXsm3r9i3cuHLn0q1r9y7evHr38u3r9y/gwIIHEy5s+DDi\nxIoXM27s+DHkyJInU65s+TLmzJo3c+7s+TPo0KJHky5t+jTq1DiqV7Nu7fo17NiyZ9Oubfs27ty6\nd/Pu7fs38ODChxMvbvw48uTKlzNv7vw59OjSp1Ovbv06dtcBAQAh+QQICgAAACwAAAAAIAEgAYf/\n///+/v79/f38/Pz7+/v6+vr5+fn4+Pj39/f29vb19fXz8/Py8vLx8fHw8PDv7+/u7u7t7e3s7Ozr\n6+vq6urp6eno6Ojn5+fm5ubl5eXj4+Pi4uLh4eHg4ODf39/e3t7d3d3c3Nzb29va2trZ2dnY2NjX\n19fW1tbV1dXT09PS0tLR0dHQ0NDPz8/Ozs7Nzc3MzMzLy8vKysrJycnIyMjHx8fGxsbFxcXDw8PC\nwsLBwcHAwMC/v7++vr69vb28vLy7u7u6urq5ubm4uLi3t7e2tra1tbWzs7OysrKxsbGwsLCvr6+u\nrq6tra2srKyrq6uqqqqpqamoqKinp6empqalpaWjo6OioqKhoaGgoKCfn5+enp6dnZ2cnJybm5ua\nmpqZmZmYmJiXl5eWlpaVlZWTk5OSkpKRkZGQkJCPj4+Ojo6MjIyLi4uKioqJiYmIiIiHh4eGhoaF\nhYWDg4OCgoKBgYGAgIB/f39+fn59fX18fHx7e3t5eXl4eHh3d3d2dnZ1dXV0dHRzc3NxcXFwcHBv\nb29ubm5tbW1sbGxra2tpaWloaGhnZ2dmZmZlZWVkZGRjY2NhYWFgYGBfX19eXl5dXV1cXFxbW1tZ\nWVlYWFhXV1dWVlZVVVVUVFRTU1NRUVFQUFBPT09OTk5NTU1MTExLS0tJSUlISEhHR0dGRkZFRUVE\nRERDQ0NBQUFAQEA/Pz8+Pj48PDw7Ozs6Ojo4ODg3Nzc2NjY0NDQzMzMyMjIwMDAvLy8uLi4sLCwr\nKysqKiooKCgnJycmJiYkJCQjIyMiIiIgICAfHx8eHh4dHR0cHBwbGxsaGhoZGRkYGBgXFxcWFhYV\n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\nli5fwowpcybNmjabNf971q3bt2/evIErV84c0aJGySElZ65cOXNOn0J1Wq6cOHHjvn0DoHUr12zZ\nto0LOy5cuHHmzJVLO24cOXLlypErV84c3bp275bLW06cOHLdugEILHhwtWrbyCEmJ07cOHPmypUj\nBw6cOHHlypEzp3kzZ83lPn82J9ocOXLlvn0DoHo1a2vWto2LPU6cuHHmzJXLPW4cOXLlfv82J3w4\n8eLCy5UTJ46cN28AnkOPLn069erWr2Nv1uxZt27fvnnzBq5cOXPmz6Mnp56cuXLlzMGPLx9+uXLi\nxI379g0A//7+AWbLtm1cwXHhwo0zZ65cw3HjyJErV45cuXLmMGbUuLH/XMdy4sSR69YNQEmTJ6tV\n20aOJTlx4saZM1euHDlw4MSJK1eOnDmfP4H6LDd0qDmj5siRK/ftGwCnT6Fas7ZtXNVx4sSNM2eu\nXNdx48iRKzd2rDmzZ9GmNVuunDhx5Lx5AzCXbl27d/Hm1buXb1+/fwEHFjyYcGHDhxEnVryYcWPH\njyFHljyZcmXLlzFn1ryZc2fPn0GHFj2adGnTp1GnVr2adWvXr2HHlj2bdm3bt3Hn1r2bd2/fv4EH\nFz6ceHHjx5EnV76ceXPnz6FHlz6denXr17Fn176de3fv38GHFz+efHnz59GnV7+efXv37+HHlz+f\nfn379/Hn17+ff3//IgABCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatyIMCAAIfkECAoAAAAs\nAAAAACABIAGH/////v7+/f39/Pz8+/v7+vr6+fn5+Pj49/f39vb29fX18/Pz8vLy8fHx8PDw7+/v\n7u7u7e3t7Ozs6+vr6urq6enp6Ojo5+fn5ubm5eXl4+Pj4uLi4eHh4ODg39/f3t7e3d3d3Nzc29vb\n2tra2dnZ19fX1tbW1dXV09PT0tLS0dHR0NDQz8/Pzs7Ozc3NzMzMy8vLysrKycnJyMjIx8fHxsbG\nxcXFw8PDwsLCwcHBwMDAv7+/vr6+vb29vLy8u7u7urq6uLi4t7e3tra2tbW1s7OzsrKysLCwr6+v\nrq6ura2trKysqqqqqampqKiop6enpqampaWlo6OjoqKioaGhoKCgn5+fnp6enZ2dnJycm5ubmpqa\nmZmZmJiYl5eXlpaWlZWVk5OTkpKSkZGRkJCQj4+Pjo6OjY2NjIyMi4uLioqKiYmJiIiIh4eHhoaG\nhYWFg4ODgoKCgYGBgICAf39/fn5+fX19fHx8e3t7eXl5eHh4d3d3dnZ2dHR0cXFxcHBwb29vbm5u\nbW1tbGxsa2traWlpaGhoZ2dnZmZmZWVlZGRkY2NjYWFhYGBgX19fXl5eXV1dXFxcW1tbWVlZWFhY\nV1dXVlZWVVVVVFRUU1NTUVFRUFBQT09PTk5OTU1NTExMS0tLSUlJSEhIR0dHRkZGRUVFREREQ0ND\nQUFBQEBAPz8/Pj4+PDw8Ozs7Ojo6ODg4Nzc3NjY2NDQ0MzMzMjIyMDAwLy8vLi4uLCwsKysrKioq\nKCgoJycnJiYmJCQkIyMjIiIiICAgHx8fHh4eHR0dHBwcGxsbGhoaGRkZGBgYFxcXFhYWFRUVFBQU\nExMTEhISEREREBAQDw8PDg4ODQ0NDAwMCwsLCgoKCQkJCAgIBwcHBgYGBQUFBAQEAwMDAgICAQEB\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACP8AAQgcSLCgwYMIEypcyLChw4cQ\nI0qcSLGixYsYM2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qcSbOmzZs4c+rcybOnz59Agwod\nSrSo0aNIkypdyrSp06dQo0qdSrWq1atYs2rdyrWr169gw4odS7as2bNo06pdy7at27dw48qdS7eu\n3bt48+rdy7ev37+AAwseTLiw4cOIEytezLixWmbMuoGbTBnct2/gwH0DB+6b52/aunXDhg1cuNOo\nUX/7pg0cOG7cvHnrli0bgNu4cydLxu3bN3DAgwf/RpwbN3DgvIED9+0bOG/QvX37Fq669erfvnHj\nlq1aNQDgw4v/V6Zs27dv4MB9W9+tGzhw3r598+YNHLhv4PKDC8e/P3+A375586bt27du3bwtzJYN\nwEOIEZMl2/bN4jdv3sB58wYO3Ldw4b59AwfuGzhw3ryB8+YN3Etw4WTOlPntW7du2q5dA9DT50+g\nQYUOJVrUaLdu4MItDQcOXDioUMGFo1o1mDdv3bqBC9fVazhv3sKF++bN27Zt4NR++wbA7Vu42rR9\nC1fX7t1w3sKFA9e3bzjA4cApU+bN27dv4MItZhxu27Zv37hlywbA8mXM3bp9C9c5HDhw4cCNBtct\n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\nqFKjggMH4CrWrFq3cu3q9StYbdq8gQMXLhw4cOHWsm3r9i3cuOHAgQNg9y7eb9/Ahevr9y9gv9/C\nES5s+DDiwt++AWjs+HG3buDCUa5s+TLmzJozgwMH4DPo0N68gQtn+jTq1OmqV7NeDQ4cgNiyZ9Ou\nbfs27ty6d/Pu7fs38ODChxMvbvw48uTKlzNv7vw59OjSp1Ovbv069uzat3Pv7v07+PDix5Mvb/48\n+vTq17Nv7/49/Pjy59Ovb/8+/vz69/Pv7x8gAIEDCRY0eBBhQoULGTZ0+BBiRIkTKVa0eBFjRo0b\nOXb0+BFkSJEjSZY0eRJlSpUrWbZ0+RJmTJkzada0eRNnTp07efb0+RNoUKFDiRY1ehRpUqVLmTZ1\n+hRqVKlTqVa1ehVrVq1buXb1+hVsWLFjyZY1exZtWrVr2bZ1+xZuXLlz6da1ezdsQAAh+QQICgAA\nACwAAAAAIAEgAYf////+/v79/f38/Pz7+/v6+vr5+fn4+Pj39/f29vb19fXz8/Py8vLx8fHw8PDv\n7+/u7u7t7e3s7Ozr6+vq6urp6eno6Ojn5+fm5ubl5eXj4+Pi4uLh4eHg4ODf39/e3t7d3d3c3Nzb\n29va2trZ2dnY2NjX19fW1tbV1dXT09PS0tLR0dHQ0NDPz8/Ozs7Nzc3MzMzLy8vKysrJycnIyMjH\nx8fGxsbFxcXDw8PCwsLBwcHAwMC/v7++vr69vb28vLy7u7u6urq5ubm4uLi3t7e2tra1tbWzs7Oy\nsrKxsbGwsLCvr6+urq6tra2srKyrq6uqqqqpqamoqKinp6empqalpaWjo6OioqKhoaGgoKCfn5+d\nnZ2cnJybm5uampqZmZmYmJiXl5eWlpaVlZWTk5OSkpKRkZGQkJCPj4+Ojo6NjY2MjIyKioqJiYmI\niIiHh4eGhoaFhYWDg4OCgoKBgYGAgIB/f39+fn59fX18fHx7e3t5eXl4eHh3d3d2dnZ1dXV0dHRz\nc3NxcXFwcHBvb29ubm5tbW1sbGxra2tpaWloaGhnZ2dmZmZlZWVkZGRjY2NhYWFgYGBfX19eXl5d\nXV1cXFxbW1tZWVlYWFhXV1dWVlZVVVVUVFRTU1NRUVFQUFBPT09OTk5NTU1MTExLS0tJSUlISEhH\nR0dGRkZFRUVERERDQ0NBQUFAQEA/Pz8+Pj48PDw7Ozs6Ojo4ODg3Nzc2NjY0NDQzMzMyMjIwMDAv\n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\nXLnNm8mRKwc6tOjQ5MiVO406terV5ciRAwA7tuzZtGvbvo07tzZt3siRCwc8nLhyxIsbP448ufJx\n4wA4fw4dnHRy5MCB8+Ytmzhx4MCNEyeuXDly5MqZP48+vXr04sQBeA8/frhw48qVI0dunP5y/Pv7\nB1hO4EBy5QweRJgQITlyABw+hAgOnDhy5MaNE5exXDly5MqRI1eunDhx48iRK5dS5UqWK8mRKydO\nHACaNW3exJlT506ePbVp80aOXDii4cSVQ5pU6VKmTZ2OGwdA6lSq/+CskiMHDpw3b9nEiQMHbpw4\nceXKkSNXTu1atm3dshUnDsBcunXDhRtXrhw5cuP8lgMcWPDgcuTKHUacWHFicuQAPIYcGRw4ceTI\njRsnTnO5cuTIlSNHrlw5ceLGkSNXTvVq1q1ZkyNXTpw4ALVt38adW/du3r19/wYeXPhw4sWNH0ee\nXPly5s2dP4ceXfp06tWtX8eeXft27t29fwcfXvx48uXNn0efXv169u3dv4cfX/58+vXt38efX/9+\n/v39AwQgcCDBggYPIkyocCHDhg4fQowocSLFihYvYsyocSPHjh4/ggwpciTJkiZPokypciXLli5f\nwowpcybNmjZv4k3MqXMnz54+fwINKnQo0aJGjyJNqnQp06ZOn0KNKnUq1apWr2LNqnUr165ev4IN\nK3Ys2bJmz6JNq3Yt27Zu38KNK3cu3bp27+LNq5dlQAAh+QQICgAAACwAAAAAIAEgAYf////+/v79\n/f38/Pz7+/v6+vr5+fn4+Pj39/f29vb19fXz8/Py8vLx8fHw8PDv7+/u7u7t7e3s7Ozr6+vq6urp\n6eno6Ojn5+fm5ubl5eXj4+Pi4uLh4eHg4ODf39/e3t7d3d3c3Nzb29va2trZ2dnY2NjX19fW1tbV\n1dXT09PS0tLR0dHQ0NDPz8/Nzc3MzMzLy8vKysrJycnIyMjHx8fGxsbFxcXDw8PCwsLBwcHAwMC/\nv7++vr69vb28vLy7u7u6urq5ubm4uLi3t7e2tra1tbWzs7OysrKxsbGwsLCvr6+urq6tra2srKyr\nq6uqqqqoqKinp6empqalpaWjo6OioqKhoaGgoKCfn5+enp6dnZ2cnJybm5uampqZmZmYmJiXl5eW\nlpaVlZWTk5OSkpKRkZGQkJCPj4+Ojo6NjY2MjIyLi4uKioqJiYmIiIiHh4eGhoaFhYWDg4OCgoKB\ngYGAgIB/f39+fn59fX18fHx7e3t5eXl4eHh3d3d2dnZ1dXV0dHRzc3NxcXFvb29ubm5tbW1sbGxr\na2tpaWloaGhnZ2dmZmZlZWVkZGRjY2NhYWFgYGBfX19eXl5dXV1cXFxbW1tZWVlYWFhWVlZVVVVU\nVFRTU1NRUVFQUFBPT09OTk5NTU1MTExLS0tJSUlISEhHR0dGRkZFRUVERERDQ0NBQUFAQEA/Pz8+\nPj48PDw7Ozs6Ojo4ODg3Nzc2NjY0NDQzMzMyMjIwMDAvLy8uLi4sLCwrKysqKiooKCgnJycmJiYk\nJCQjIyMiIiIgICAfHx8eHh4dHR0cHBwbGxsaGhoZGRkYGBgXFxcWFhYVFRUUFBQTExMSEhIREREQ\nEBAPDw8ODg4NDQ0MDAwLCwsKCgoJCQkICAgHBwcGBgYFBQUEBAQDAwMCAgIBAQEAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAI/wABCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPH\njyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iTKl3KtKnTp1Cj\nSp1KtarVq1izat3KtavXr2DDih1LtqzZs2jTql3Ltq3bt3Djyp1Lt67du3jz6t3Lt6/fv4ADCx5M\nuLDhw4gTK17MuLFabdrAiZssLlw4cZgzYw7HOZy4z5/DiRZHunTpcONSqx4HDhyA17Bja9MWTpzt\n2+Ny6xbHu/c4ccCDixtHvHhxcePGiVu+HBw4ANCjS9emDZy469ixj9surrt3cOHCgf8DF06c+fPh\n0otbv37cOHHwv30DQL++/W3bwo0bJ66/f4DiBI4jWNBguHDdtm3z5g3cQ3ERxYWjWJGiN28ANG7k\n2NHjR5AhRY4MF07cyZPhwokb19LlS5gtw4UbN06cuHHidO4UN86nT3HiAAwlWhQcOHHjlC5lOk7c\nOKhRo4qjSnXcVaxYxW0NF07c13DhAIwlWxYcuHDi1K4d19ZtW3FxxYETV7fuOLzjxO399m3cOHHj\nBA8eJ04cAMSJFYMDJ27cY8iRJU8WJ66bOHHjNG/eHE7cZ9DhwIEDUNr0adSpVa9m3dr1ONixx4kT\n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\nK9iw48aSLWv2LNq0aseJEwfgLdy44+bSrWv3Lt68eukC6Ov377jAggcTLmz4MGLBABYzbuz4MeTI\nkidT5sYt3LjMmjdz7uz5s2dx4gCQLm362zdx/+NWs27t+jXs2LDFiQNg+zZucODEjevt+zfw4MKH\nEwdg/DhycODEjWvu/Dn06NKnUwdg/Tr27Nq3c+/u/Ts3buHGkS9v/jz69OrTixMH4D38+N++iRtn\n/z7+/Pr3898vDqA4AAMJFgQHTtw4hQsZNnT4EGJEABMpVgQHTtw4jRs5dvT4EWRIACNJljR5EmVK\nlStZtnT5EmZMmTNp1rR5E2dOnTt59vT5E2hQoUOJFjV6FGlSpUuZNnX6FGpUqVOpVrV6FWtWrVu5\ndvX6FWxYsWPJljV7Fm1atWvZtnX7Fm5cuXPp1rV7F29evXv59vX7F3BgwYMJFzZ8GHFixYsZNyp2\n/BhyZMmTKVe2fBlzZs2bOXf2/Bl0aNGjSZc2fRp1atWrWbd2/Rq24YAAIfkECAoAAAAsAAAAACAB\nIAGH/////v7+/f39/Pz8+/v7+vr6+fn5+Pj49/f39vb29fX18/Pz8vLy8fHx8PDw7+/v7u7u7e3t\n7Ozs6+vr6urq6enp6Ojo5+fn5ubm5eXl4+Pj4uLi4eHh4ODg39/f3t7e3d3d3Nzc29vb2tra2dnZ\n2NjY19fX1tbW1dXV09PT0tLS0dHR0NDQz8/Pzs7Ozc3NzMzMy8vLysrKycnJyMjIx8fHxsbGxcXF\nw8PDwsLCwcHBv7+/vr6+vb29vLy8u7u7urq6ubm5uLi4t7e3tra2tbW1s7OzsrKysbGxsLCwr6+v\nrq6uq6urqqqqqampqKiop6enpqampaWlo6OjoqKioaGhoKCgn5+fnp6enZ2dnJycm5ubmpqamZmZ\nl5eXlpaWlZWVk5OTkpKSkZGRj4+Pjo6OjY2NjIyMi4uLioqKiYmJiIiIh4eHhYWFg4ODgoKCgYGB\ngICAf39/fn5+fX19fHx8e3t7eXl5eHh4d3d3dnZ2dXV1dHR0cXFxcHBwb29vbm5ubW1tbGxsa2tr\naWlpaGhoZ2dnZmZmZWVlZGRkY2NjYWFhYGBgX19fXl5eXV1dXFxcW1tbWVlZWFhYV1dXVlZWVVVV\nVFRUU1NTUVFRUFBQT09PTk5OTU1NTExMS0tLSUlJSEhIR0dHRkZGRUVFREREQ0NDQUFBQEBAPz8/\nPj4+PDw8Ozs7Ojo6ODg4Nzc3NjY2NDQ0MzMzMjIyMDAwLy8vLi4uLCwsKysrKioqKCgoJycnJiYm\nJCQkIyMjIiIiICAgHx8fHh4eHR0dHBwcGxsbGhoaGRkZGBgYFxcXFhYWFRUVFBQUExMTEhISERER\nEBAQDw8PDg4ODQ0NDAwMCwsLCgoKCQkJCAgIBwcHBgYGBQUFBAQEAwMDAgICAQEBAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACP8AAQgcSLCgwYMIEypcyLChw4cQI0qcSLGi\nxYsYM2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qcSbOmzZs4c+rcybOnz59AgwodSrSo0aNI\nkypdyrSp06dQo0qdSrWq1atYs2rdyrWr169gw4odS7as2bNo06pdy7at27dw48qdS7eu3bt48+rd\ny7ev37+AAwseTLiw4cOIEytezLixWm/evoGbTLmy5cuYM2sGwLmzZ2/ewIkeTbq06dOoU4MDwLq1\na2/evoGbTbu27du4c+sGwLu3b2/ewAkfTry48ePIkX/7BqC58+fQo0ufTr26dXDYs2vfzr279+/Z\nAYj/H08enPnz6NOrX8++/XkA8OPL//YNnP37+PPr38+/PziAAAQOJAjO4EGECRUuZNjwIACIESVO\npFjR4kWMGcFt5NjR40eQIUVyBFDS5ElwKVWuZNnS5UuYKgHMpFkT3E2cOXXu5NnTJ04AQYUOBVfU\n6FGkSZUuZWoUwFOoUaVOpVrV6lWs4LRu5drV61ewYbcCIFvWLDi0adWuZdvW7du0AOTOpQvO7l28\nefXu5dv3LgDAgQWDI1zY8GHEiRUvLgzA8WPIkSVPplzZ8mVwmTVv5pzZmzdwob99A1fa9GnUqU0D\nYN3aNTjYsWXPpl3b9u3YAHTv5g3O92/gwX1/I/5t/9u3b968gWPe3Plz6OAATKdeHdx17Nmza9u2\nrVs3cOHDe/MGzvx59OnVgwPQ3v17+PHlz6df3z44/Pn178fvzRtAcAK/fQNn8CDChAoPAmjo8CG4\niBInUqxo8SJGiQA2cuwI7iPIkCI/fiv5bdu3b968gWvp8iXMmOAA0KxpExzOnDp1atu2rVs3cEKF\n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\nAAAsAAAAACABIAGH/////v7+/f39/Pz8+/v7+vr6+fn5+Pj49/f39vb29fX18/Pz8vLy8fHx8PDw\n7+/v7u7u7e3t7Ozs6+vr6urq6enp6Ojo5+fn5ubm5eXl4+Pj4uLi4eHh4ODg39/f3t7e3d3d3Nzc\n29vb2tra2dnZ2NjY19fX1tbW1dXV09PT0tLS0dHR0NDQz8/Pzs7Ozc3NzMzMy8vLysrKycnJyMjI\nx8fHxsbGxcXFw8PDwsLCwcHBwMDAv7+/vr6+vb29vLy8u7u7urq6ubm5uLi4tra2tbW1s7OzsrKy\nsbGxsLCwr6+vrq6ura2trKysq6urqqqqqampqKiop6enpqampaWlo6OjoqKioaGhoKCgnp6enZ2d\nnJycm5ubmpqamZmZmJiYl5eXlpaWlZWVk5OTkpKSkZGRkJCQj4+Pjo6OjY2NjIyMi4uLioqKiYmJ\niIiIh4eHhoaGhYWFg4ODgoKCgYGBgICAf39/fn5+fX19fHx8e3t7eXl5eHh4d3d3dnZ2dXV1dHR0\nc3NzcXFxcHBwb29vbm5ubW1tbGxsa2traWlpaGhoZ2dnZmZmZWVlZGRkY2NjYWFhYGBgX19fXl5e\nXV1dXFxcW1tbWVlZWFhYV1dXVlZWVVVVVFRUU1NTUVFRUFBQT09PTk5OTU1NTExMS0tLSUlJSEhI\nR0dHRkZGRUVFREREQ0NDQUFBQEBAPz8/Pj4+PDw8Ozs7Ojo6ODg4Nzc3NjY2NDQ0MzMzMjIyMDAw\nLy8vLi4uLCwsKysrKioqKCgoJycnJiYmJCQkIyMjIiIiICAgHx8fHh4eHR0dHBwcGxsbGhoaGRkZ\nGBgYFxcXFhYWFRUVFBQUExMTEhISEREREBAQDw8PDg4ODQ0NDAwMCwsLCgoKCQkJCAgIBwcHBgYG\nBQUFBAQEAwMDAgICAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACP8AAQgcSLCgwYMIEypcyLCh\nw4cQI0qcSLGixYsYM2rcyLGjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qcSbOmzZs4c+rcybOnz59A\ngwodSrSo0aNIkypdyrSp06dQo0qdSrWq1atYs2rdyrWr169gw4odS7as2bNo06pdy7at27dw48qd\n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\n8XDhyjFv7vw5dOgAplOvbv069uzat3MnR64c+PDix5Mvb/58OQDq17MnR64c/Pjy59OPT64c/vz6\n9/MH4B8gAIEDAZQzeBBhQoTixAkp9xBiRIkTJQKweBFjOY0bOXb0+BFkyI0ASJY0eRJlSpUrWbYk\nR65cTJkzada0eRNnOQA7efYkR65cUKFDiRYVSq5cUqVLmTYF8BRq1HJTqVa1WlWcOCHluHb1+hXs\nVwBjyZYtdxZtWrVr2bZ1ixZAXLlz6da1exdvXr3l+Pb1+xdwYMGD+wIwfBhxOcWLGTf/dtyYXDnJ\nkylXtgwAc2bN5Th39vzZ87dv5UiXNn0aNWoAq1m3LvcadmzZs2nXtg0bQG7du3n39v0beHDh5YgX\nN34ceXLly4sDcP4c+rhx5ahXt34de3bt28sB8P4dfDnx48mXN38effrxANi3d18Ofnz58+nXt38/\nPgD9+/n39w8QgMCBBAsaPIgwYbmFDBs6fAgxokSGACpavDhuXLmNHDt6/AgypMhyAEqaPFkupcqV\nLFu6fAlTJYCZNGuWu4kzp86dPHv6xAkgqNChRIsaPYo0qdKlTJs6fQo1qtSpVKtavYo1q9atXLt6\n/Qo2rNixZMuaPYs2rdq1bNu6fQs3bK7cuXTr2r2LN6/evXz7+v0LOLDgwYQLGz6MOLHixYwbO34M\nObLkyZQrW76MObPmzZw7e/4MOrTo0aRLmz6NOrXq1axbu34NO7bs2bRr276NO7fu3bx7+/4NPLjw\n4cSLGz+OPLny5cybOycdEAAh+QQICgAAACwAAAAAIAEgAYf////+/v79/f38/Pz7+/v6+vr5+fn4\n+Pj39/f29vb19fXz8/Py8vLx8fHw8PDv7+/u7u7t7e3s7Ozr6+vq6urp6eno6Ojn5+fm5ubl5eXj\n4+Pi4uLh4eHg4ODf39/e3t7d3d3c3Nzb29va2trZ2dnY2NjX19fW1tbV1dXT09PS0tLR0dHQ0NDP\nz8/Ozs7Nzc3MzMzLy8vKysrJycnIyMjHx8fGxsbFxcXDw8PCwsLBwcHAwMC/v7++vr69vb28vLy7\nu7u6urq5ubm4uLi3t7e2tra1tbWzs7OysrKxsbGwsLCvr6+urq6tra2srKyrq6uqqqqpqamoqKin\np6empqalpaWjo6OioqKhoaGgoKCenp6dnZ2cnJybm5uampqZmZmYmJiXl5eWlpaVlZWTk5OSkpKR\nkZGQkJCPj4+Ojo6NjY2MjIyLi4uKioqJiYmIiIiHh4eGhoaFhYWDg4OCgoKBgYGAgIB/f39+fn59\nfX18fHx7e3t5eXl4eHh3d3d2dnZ1dXV0dHRzc3NxcXFwcHBubm5tbW1sbGxra2tpaWloaGhnZ2dm\nZmZlZWVkZGRjY2NhYWFgYGBfX19eXl5cXFxbW1tZWVlYWFhXV1dWVlZVVVVUVFRTU1NRUVFQUFBO\nTk5NTU1MTExLS0tJSUlISEhHR0dGRkZFRUVERERDQ0NBQUFAQEA/Pz8+Pj48PDw7Ozs6Ojo4ODg3\nNzc2NjY0NDQzMzMyMjIwMDAvLy8uLi4sLCwrKysqKiooKCgnJycmJiYkJCQjIyMiIiIgICAfHx8e\nHh4dHR0cHBwbGxsaGhoZGRkYGBgXFxcWFhYVFRUUFBQTExMSEhIREREQEBAPDw8ODg4NDQ0MDAwL\nCwsKCgoJCQkICAgHBwcGBgYFBQUEBAQDAwMCAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAI/wABCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJ\nsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iTKl3KtKnTp1CjSp1KtarVq1izat3KtavX\nr2DDih1LtqzZs2jTql3Ltq3bt3Djyp1Lt67du3jz6t3Lt6/fv4ADCx5MuLDhw4gTK17MuLHacOHE\njZtMubLly5bFaRYXjhu3bdvCiRM3rrTpcQBSq14tTty417Bjy55Nu7btcQBy694tTty438CDCxcO\nrji4b+HCffsWrrm45+LGSZ9OHYD169jDhRM3rrv3cOLCh/8PF26c+fPo06tPL649OHAA4sufT7++\n/fv48+sfx7+/f4DjBA4kWNBguHDjxIkDB27cQ4gRAUykWHHcRYwZNW7k2NEjRgAhRY4cV9LkSZQp\nx4ED581bOHHiwIELF27cTZw5dQLg2dPnOKBBg4obV3ScOKTjlC5l2tTp06UApE6lWtXqVaxZtW4d\n19XrV7BhxXoVJ26cOHHj1K5lqxbAW7hxx82lW9fuXbx59dIF0Nfv33GBBQ8mXFgcN8TcxC1ePM7x\nY8iRHQOgXNnyOMyZNWsW13ncZ9ChRY8mDRrAadSpVa9m3dr1a9jjZM+mXdv27dnixI0TJ27cb+DB\nfwMgXtz/+DjkyZUvZ97c+fPkAKRPpz7O+nXs2bWL49admzjw4MeNJ1/e/HgA6dWvH9fe/fv34uSP\no1/f/n38+esD4N/fP0AAAgcSLGjwIMKECgGMa+jwIcSIEidSdAjgIsaM4zZy7OhRnLhw4caRLGny\nJMqU4wCwbOlyHMyYMmfS/LZs2bZt4sbx7OnzJ1AAQocSHWf0KNKk4sSNa+p0nDhx46ZSrWr16jgA\nWrdy7er1K9iwYseOK2v2LNq0ateyNQvgLdy44+bSrWtXnLhw4cbx7ev3L+DA4wAQLmx4HOLEihcz\n/rZs2bZt4sZRrmz5MmYAmjdzHuf5M+jQ4sSNK216nDhx/+NWs27t+vU4ALJn065t+zbu3Lp3j+vt\n+zdwceLChSNGjNaRI8eOuQIGTJmycdKnU68uHQD27NrHce/u3bs1MGA+fcqWTdy49OrXs2/PHgD8\n+PLH0a8/Tpy4cfr3c+MGDSA0Q4ZciBAxaxa4cQsZNnT4EEBEiRPHVbR48aK4bNm2bTt1akuBAhUq\nWEiRokMHcODGtXT5EiYAmTNp1rR5E2dOnTvH9fT5E6g4ceHCESNG68iRY8dcAQOmTNk4qVOpVpUK\nAGtWreO4dvXq1RoYMJ8+ZcsmblxatWvZtmULAG5cuePo1h0nTtw4vXu5cYMGzZAhFyJEzJoFblxi\nxYsZN/8G8Bhy5HGTKVeuLC5btm3bTp3aUqBAhQoWUqTo0AEcuHGrWbd2DQB2bNmzade2fRt37nG7\nd4sTN26cuHHDx1Xz4mXBggEDBABwDiCABg2fPo2zfh17dusAuHf3Pg58ePHgxYkbpkKFBg2qVIVz\nPw4+fHHixtWvz4zZqW3bwIETB1DcuIEACho8OG6cuIXevG3bNu3aNW3aQnXogABBgwYdLFggQkTb\nuJEkS5o8CSClypXjWrp8CdObN02aJkwIACCnzp0AzIz7CTRoUABEixo9ijSp0qVMm457ChVqOHHi\ntm2LMWAAAAABAgD4CnbCBGrUxpk9izatWQBs27odBzf/rly44sQREyFiwYIcOZJBg6ZNG7NfvyJF\natMGzIIFAgQAkCChRYthw8CJEwcgs+bN4zp3FieuWjVVgwYhQSIAgGoABQosUKDgwoVM4cKJEzcu\nt+7dvHMD+A08+LjhxIsbHx4r1oABAJo7f+5ckqRx1Ktbpw4gu/bt3Lt7/w4+vPhx5MuXDydO3LZt\nMQYMAAAgQAAA9OtPmECN2rj9/Pv7BzhuHACCBQ2OQ5hQIUJx4oiJELFgQY4cyaBB06aN2a9fkSK1\naQNmwQIBAgBIkNCixbBh4MSJAxBT5sxxNWuKE1etmqpBg5AgEQBAKIACBRYoUHDhQqZw4cSJGxdV\n6lSq/1EBXMWaddxWrl29bo0Va8AAAGXNnjUrSdI4tm3dsgUQV+5cunXt3sWbV+84vn39+s1myxY0\naNeuASNBIsDiYsXGPYYcWXJkAJUtXx6XWfNmzrVqPXkSJEizX79YsSoiSdKOHSFCEICdIMGCQIGY\nMQsXbpw4cQB8/wY+TrhwceLChXuWLJkWLRAWLLBggQwZUVSo+PCBBxu2b9/GfQcfXvx3AOXNnx+X\nXv169unDhfvwIQAAAALsDxgAQL/+IEHGARwncOBAceIAIEyocCHDhg4fQow4biLFihWz2bIFDdq1\na8BIkAggslixcSZPokyJEgDLli7HwYwpc2atWk+eBP8J0uzXL1asikiStGNHiBAEjiZIsCBQIGbM\nwoUbJ04cgKpWr47LmlWcuHDhniVLpkULhAULLFggQ0YUFSo+fODBhu3bt3F27+LNaxcA375+xwEO\nLHgw4HDhPnwIAACAgMYDBgCIHDlIkHGWL2MWJw4A586eP4MOLXo06dLjTqNOrXr1OGfOFCggMG42\n7dq2bwPIrXv3uN6+fwPv0UOBAgECHhQoQIAAgAABAAAIEAAAAwaOHInLPm4793EAvoMPP248+XHh\nwg1TpsyFCwPukSAxY2ZNggQIEBDo0EGCBEaMABobN5BgwYIAECZUOI5hQ4cPGYYLN2AAAIsLFrxY\nsSL/QAAAHyNEiBRpXEmTJwGkVLmSZUuXL2HGlDmOZk2bN3GO27btwAEV44AGFTqUKACjR5GOU6pU\nnLhxT6GOw6VAAQAAAQIIALCVa1cAB8SJGzeWbNmxANCmVTuOLVtx4sCBK1amjBEjGRw4QIHixg0Q\nBAgEEFygQADDASRo0yZO3DjHjyEDkDyZ8jjLlzFnthwjBgDPngsUMPTp04EDAFAvWCBDBiZs2MTF\nFjdOnDgAt3Hn1r2bd2/fv4GPEz6ceHHj47ZtO3BAxTjnz6FHlw6AenXr47BjFyduXHfv43ApUAAA\nQIAAAgCkV78ewAFx4sbFlz8/PgD79/GP069fnDhw/wDBFStTxoiRDA4coEBx4wYIAgQCSCxQIIDF\nABK0aRMnbpzHjyABiBxJcpzJkyhTmowRA4BLlwUKGPr06cABADgXLJAhAxM2bOKCihsnThyAo0iT\nKl3KtKnTp1DHSZ1KtarVcaBABQggZ5zXr2DDigVAtqzZcWjTphXHtlq1EQMGAAAwYACAu3jz3oUz\nrq/fv38BCB5MeJzhw+PChfuFBUuRIg8oUPjw4cEDAQEyB2BAgECAAAAAGHj2bJzp06hNA1jNuvW4\n17Bjy34tQACA27cbNCjDggWA378VKAgT5pc2bdu2jVu+HIDz59CjS59Ovbr16+Oya9/Ovfs4UKAC\nBP+QM668+fPo0wNYz779uPfw4YubX63aiAEDAAAYMACAf4AABA4cCGfcQYQJEwJg2NDhOIgRx4UL\n9wsLliJFHlCg8OHDgwcCAowMwIAAgQABAAAw8OzZOJgxZcIEUNPmzXE5de7kmVOAAABBgzZoUIYF\nCwBJkypQECbML23atm0bV7UqAKxZtW7l2tXrV7Bhx40lW9bs2WoRIihQgG3cW7hx5c4FUNfu3XF5\n9e7NK04cMkWK7NiZMwfHgQMDBgQYMCBAAA0axI2jXNmyZQCZNW8e19mzZ3ChuXE7BgjQoEEsWLh4\n8gQVKmKzZm3YYMCACXHixu3m3Xs3AODBhY8jXtz/+HHiFiwAAHBAipRp07ZBgmTAwIABPcKFG9fd\n+/fuAMSPJ1/e/Hn06dWvH9fe/Xv48atFiKBAAbZx+fXv598fAEAAAgcOHGfwIEKD4sQhU6TIjp05\nc3AcODBgQIABAwIE0KBB3LiQIkeOBGDyJMpxKleuBOeSG7djgAANGsSChYsnT1ChIjZr1oYNBgyY\nECduHNKkSpECaOr06bioUqdSjWrBAgAAB6RImTZtGyRIBgwMGNAjXLhxateyVQvgLdy4cufSrWv3\nLt5xevfy5SuOGTNhwlq0MAAAAAMGtsYxbuz4MWQAkidTHmf5MubM4jaLixUrxoEDChQQECAAAAAJ\n/xKujWvt+vVrALJn0x5n+zbu3ODAZcsmTFizb9/GEQ8XDgSIAAFajGvu/PlzANKnUx9n/Tr27Nbx\n4BEhgla3buPGNdOgQYAABAgsjWvv/v17APLn069v/z7+/Pr3j+vvH+A4gQPBgXtFgYIBAwECAHA4\nYIAPWbJ48dq2bVxGjRs5AvD4EeQ4kSNJlhRZrNiTJw4ECDhwQMGAAQBoAgAxDmdOnToB9PT5c1xQ\noUOJFi0aLlyHDgAAjBr3FGrUqACoVrU6DmtWrVuxevGiS9c4seDAGVOhYsCABAm6jXP7Fi5cAHPp\n1rV7F29evXv5jvP7F7BfcOBeUaBgwECAAAAYD/8Y4EOWLF68tm0bdxlzZs0AOHf2PA50aNGjQRcr\n9uSJAwECDhxQMGAAANkAQIyzfRs3bgC7efce9xt4cOHDh4cL16EDAACjxjV3/vw5AOnTqY+zfh17\ndutevOjSNQ48OHDGVKgYMCBBgm7j2Ld37x5AfPnz6de3fx9/fv3j+Pf3D3DcOHDgrCxYECBhAAAC\nBBw4MCBiggSCBIkbhzGjRo0AOnr8OC6kyJEkQ86ZQ4AAgAABEiRgESAAgJkzDxxw5myczp08Afj8\nCXSc0KFEixo1yo1bggQAAHQbBzWqVKkAqlq9Oi6r1q1csxIixI3buLHQoLUIEAAAgAABpo17Czf/\nblwAdOvavYs3r969fPuO+ws48F9w4KwsWBAgcQAAAgQcODAgcoIEggSJG4c5s2bNADp7/jwutOjR\npEPPmUOAAIAAARIkYBEgAIDZsw8ccOZsnO7dvAH4/g18nPDhxIsbN86NW4IEAAB0Gwc9unTpAKpb\nvz4uu/bt3LMTIsSN27jx0KC1CBAAAIAAAaaNew8/fnwA9Ovbv48/v/79/PuPAzhO4ECCBMOFAwdu\n3MKF4sS5SpFiwQJAgMZdxJhRIwCOHT2OAxlS5Ehx4jZsQIBgDjhw41y6bNWqRw8DKFAcOzZO506e\nAHz+BDpO6FCiRY0ahQbNgYMdO8Y9hRpVKgCq/1WtjsOaVevWcOFo0apWbVy3bowYLThwQICABg2w\njYMbV65cAHXt3sWbV+9evn39jgMcWPBgwoTPnBkwYMWKbOMcP4YMGcBkypXHXb4sTpw0ad7GfR4n\nDggQAgSwYBmXWvVqb94CAIANYNo42rVrA8CdW/c43r19/wb+W1ycOAUKQIM2Tvly5s0BPIcefdx0\n6tWrDxMgIECAAwdKMGAgQAAA8uQDBNgwTv169uwBvIcfX/58+vXt38c/Tv9+/v39AxwncOCZMwMG\nrFiRbRzDhg4dAogoceK4ihXFiZMmzdu4juPEAQFCgAAWLONOokzpzVsAAC4BTBsnc+ZMADZv4v8c\np3Mnz54+e4qLE6dAAWjQxiFNqnQpgKZOn46LKnXq1GECBAQIcOBACQYMBAgAIFZsgAAbxqFNq1Yt\ngLZu38KNK3cu3bp2x40TN27ct2/hwoEbJ3gw4cKCpUk7cIABA2vjHkOOHBkA5cqWx40L9+1bpUpx\n4qhJlMiXLxwBAiBAIE7cuNauX/PiFQAAbQApxuHOnRsA796+xwEPLnw48eDhwvUKEgQDBnHixkGP\nLn06gOrWr48bJ27cOG7cwIHzFi0aIkQAzp8PEGBAgAAA3gcIAGA+gABz5nDjNm4///4AAAIQOJBg\nQYMHESZUqHDcOHHjxn37Fi4cuHEXMWbUeFH/mrQDBxgwsDaOZEmTJgGkVLly3Lhw375VqhQnjppE\niXz5whEgAAIE4sSNEzqUKC9eAQAkBZBiXFOnTgFElTp1XFWrV7FmtRouXK8gQTBgECduXFmzZ9EC\nULuW7bhx4saN48YNHDhv0aIhQgSAL98AAQYECACAcIAAABADCDBnDjdu4yBHlgyAcmXLlzFn1ryZ\nc+dxnz+LE+fNW7dxp1GnVn16zpwDBwYMuDWOdm3btgHk1r1bnLhw3LjlytWhQ4IBAwIEALDchYtx\nz6FHf+7KFQDr1meM0759OwDv38GPEz+efHnz43v1ujFhAiBA4+DHlz8fPgD79/GP068/XLhr/wCv\niXLgAIDBgwAECAgAoCEAAQAiSgQgQAANGtrGady4EYDHjyBDihxJsqTJk+NSphQnzpu3buNiypxJ\nM+acOQcODBhwa5zPn0CBAhhKtKg4ceG4ccuVq0OHBAMGBAgAoKoLF+Oyat2a1ZUrAGDBzhhHtmxZ\nAGjTqh3Htq3bt3Db9up1Y8IEQIDG6d3Lt69eAIADCx5HmHC4cNeuiXLgAIDjxwAECAgAoDIAAQAy\nawYgQAANGtrGiR49GoDp06hTq17NurXr1+NixxYnzps3ZeLEjdvNu7dvMGAGDDhwgNm448iTJwfA\nvLnzcdChT5vGihWNChUCBBCAAAEwYOPCi/8fH75DBwDoBQgoNa69e/cA4sufP66+/fv489vHhs3Q\nLYC3tGkbV9DgQYQFASxk2HDcw4fiJIq7ZskSChQXdOiYNevYsVuRIt26JQgJEgMGCBAQIEECDhzB\nxs2kSRPATZw5de7k2dPnT6DjhA4dJ07cIz9+TJiYtW3bOKhRxzFjNgDAVQABAoga19Xr168AxI4l\nO87s2XHhwtHCgQPA27dLljx6hEuDBho0tsSJs2ABAMCACxTwNs7w4cMAFC9mPM7xY8iRJT8WJ07R\nZT58xm3m3NnzZgChRY8eV9r06dLixI1j3dp1a3HiLl2KESMBBAgPHoTy5m3cb+DjAAwnXtz/+HHk\nyZUvZz7O+fNx4sQ98uPHhIlZ27aN4959HDNmAwCMBxAggKhx6dWvXw/A/Xv44+TPHxcuHC0cOADs\n379kCcBHj3Bp0ECDxpY4cRYsAODQYYEC3sZRrFgRAMaMGsdx7OjxI8iO4sQpKsmHz7iUKleyTAng\nJcyY42bSrDlTnLhxOnfy3ClO3KVLMWIkgADhwYNQ3ryNa+p0HICoUqdSrWr1KtasWsdx7dp1WoQI\nAQIACBAAAgQ6dHgYMADg7dsAAQYM4DbuLt68eQHw7et3HODAgcGRINGggQUGDAgQCBAAAOTIAwYE\nCADgcq1a3bqJG+f582cAokeTHmfatDhx/+NWs27tuvWmKFFatPDlaxzu3Lp3A+jt+7e44OOGEy9u\n/LjxcOHUqGkhRsyLF6HChRtn/fo4ANq3c+/u/Tv48OLHjytv3vy0CBECBAAQIAAECHTo8DBgAAB+\n/AECDBjADeA4gQMJEgRwEGHCcQsZMgRHgkSDBhYYMCBAIEAAABs5DhgQIAAAkbVqdesmblxKlSoB\ntHT5clzMmOLEjbN5E2dOnJuiRGnRwpevcUOJFjUKAGlSpeKYjnP6FGpUqVHDhVOjpoUYMS9ehAoX\nblxYseMAlDV7Fm1atWvZtnU7Dm7cuOL+/FmwAEBevXvzBoAAoUuXcOHGFTZ8GDEAxYsZj/9z/Pix\nuGDBrFlrJkuWIUMIEADw7BmFBQsHDiBAcGlcatWrVwNw/Rr2ONmzade2XZuXAQMSJAgTNg54cOHD\nARQ3flycuHHLmTd3/hz6uGvXIAkRggYNt3HbuXMH8B18ePHjyZc3fx79OPXr2bP3RoQIBAgG6H/4\n8OzZOP37+ff3D3AcgIEEC447iDChwoXgnj0bBzGixIkUIwK4iDHjuI0cO3r86NFbpEiPHokTNy6l\nypUsAbh8CXOczJk0a9q8iVOcuHE8e/oEADSo0KFEixo9ijTpuKVMmzb1RoQIBAgGqn748OzZuK1c\nu3r9Og6A2LFkx5k9izatWnDPno17Czf/rty5cAHYvYt3nN69fPv67estUqRHj8SJG4c4seLFABo7\nfjwusuTJlCtbvixO3LjNnDsD+Aw6tOjRpEubPo16nOrVrFu7fg079moAtGvbHoc7t+7dvHv7/p0b\ngPDhxMcZP448ufLk4MKFGwc9uvTp0gFYv459nPbt3Lt7/w4+/HYA5MubP48+vfr17NuPew8/vvz5\n9Ovbhw8gv/794/r7BzhO4ECCBQ0eRGgQwEKGDcc9hBhR4kSJ4MKFG5dR40aOGwF8BBly3EiSJU2e\nRJlSJUkALV2+hBlT5kyaNW2Ow5lT506ePX3+zAlA6FCi44weRZpU6VKmTY8CgBpV6jiq/1WtXhUn\nbtxWrl29fgXLFcBYsmXHnUWbVu1atm3dogUQV+5cunXt3sWbV+84vn39/gUcWPDgvgAMH0Y8TvFi\nxo0dP4YceTEAypUtj8OcWfNmceLGfQYdWvRo0qABnEadetxq1q1dv4YdWzZrALVt38adW/du3r19\njwMeXPhw4sWNHw8OQPly5uOcP4ceXfp06tWfA8CeXfs47t29fwcfXvz47gDMn0c/Tv169u3dv4cf\nfz0A+vXt38efX/9+/v3HARwncCDBggYPIkwoEADDhg7HQYwocSLFihYvRgSgcSPHcR4/ggQpLly4\ncSZPokypcuVJAC5fwhwncybNmjZv4v/MORMAz54+fwINKnQo0aLjjiJNqnQp06ZOkQKIKnXquKpW\nr2LNqnUrV6sAvoINO24s2bJlxYULN24t27Zu38JlC2Au3brj7uLNq3cv375+8QIILHgw4cKGDyNO\nrHgx48aOH0OOLHky5cqWL2POrHkz586eP4MOLXo06dKmT6NOrXo169auX8OOLXs27dq2b+POrXs3\n796+fwMPLnw48eLGj3cOp3wc8+bixkGHDg6cuOrWxY3Lrn079+7aAYAPL16cuHHmz6NPr349+/bj\nAMCPLz9cOHHj7uPPf1+cuHHjAIoTFw4cOHHixiVUuJBhw3EAIEaUKE7cOIsXMWbUuJH/Y8dxAECG\nFDmSZEmTJ1GmDLdyXEuX4sbFjAkOnDibN8WN07mTZ0+fOwEEFTpUnLhxR5EmVbqUaVOn4wBElTo1\nXDhx47Bm1YpVnLhx48SJCwcOnDhx49CmVbuW7TgAb+HGFSduXF27d/Hm1buX7zgAfwEHFjyYcGHD\nhxGPU7yYcWNw4MRFjjyOcmXLlzFfBrCZc+dxn0GHFj2adGnToAGkVr16XGvXr2GLEzdunDhx4cCB\nEyduXG/fv4EHHweAeHHj45AnV76ceXPnz5MDkD6denXr17Fn1759XHfv38GDAxcu3Lhx4salV7+e\nfXv2AODHlz+Ofn379/Hn17+/PgD//wABCBwIYJzBgwgTKhQ3rqHDhxAjQgRAsaLFcRgzatzIsSNH\ncOC+fRtHsqRJAChTqlzJsqXLlzBjjptJs6ZNcODChRs3Tty4n0CDCh0qFIDRo0jHKV3KtKnTp1Cj\nLgVAtarVcVizat3KVdy4r2DDih0rFoDZs2jHqV3Ltq3bt27Bgfv2bZzdu3gB6N3Lt6/fv4ADCx48\nrrDhw4jFiRvHuLHjx5AjNwZAubLlcZgza97MubPnz5kBiB5Nepzp06hTq17NuvVpALBjyx5Hu7bt\n27hz48aGDRq0ccCDCwdAvLjx48iTK1/OvPm459CjSxcnbpz169iza99+HYD37+DHif8fT768+fPo\n048HwL69+3Hw48ufT7++/fvxAejfz3+cf4DjBA4kWNDgwYHYsEGDNs7hQ4gAJE6kWNHiRYwZNW4c\n19HjR5AhQYZDhYoECW7cxq1k2dIlAJgxZY6jWdPmTZw4v3378sWYMXDjhA4lShTAUaRJxy1l2tTp\nU6hOHYEAsWLFOKxZtQLg2tXrOLBhxY4lS4wQoXDhxq1lO46JAAEAACTy5m3cXbzjAOzl29fvX8CB\nBQ8mPM7wYcSJFScOhwoVCRLcuI2jXNnyZQCZNW8e19nzZ9ChQ3/79uWLMWPgxq1m3bo1ANixZY+j\nXdv2bdy5bzsCAWLFinHBhQ8HUNz/+PFxyZUvZ96cGCFC4cKNo159HBMBAgAASOTN2zjw4ccBIF/e\n/Hn06dWvZ99+3Hv48eXPh+/NmxgBAgAACBDgBEBIkHr1yjbuIEKEABYybDhunLiI4cKJExduHMaM\nGjWKEzdNggQAAAQIsDPuJMqUKQGwbOlyHMyYMmfSrBnzxw8AOnVyCBduHNCg4wAQLWp03Dhx45Yy\nbbo0XLhVq6ZMWYAAgSVL47aKE8eDhwAAYsVKkRIu3Li0aQGwbev2Ldy4cufSrTvuLt68evfilSYt\nAIDAggEECIAAgQlw4MYxbjwOAOTIksdRrjzOmzdr4cKN6+z5szhxSJAUAGD6NIFq/9XGsW7tmjWA\n2LJnj6tt+zbu3LptBwgA4DfwBg06dfLmbRxyAMqXMx/n/Dn06NWqESAA4Pr1Bg1mbdr04gWA8OLD\nGzCwbNm49OkBsG/v/j38+PLn068/7j7+/Pr345cmDWAAAAMJAggQAAECE+DAjXP4cBwAiRMpjrN4\ncZw3b9bChRv3EWRIceKQICkAAGVKAtWqjXP5EqZLADNp1hx3E2dOnTt54gwQAEBQoQ0adOrkzds4\npQCYNnU6DmpUqVOrVSNAAEDWrA0azNq06cULAGPJjjVgYNmycWvXAnD7Fm5cuXPp1rV7d1xevXv5\n9tUbKxYAwYIXLNhkzFi2bN7GNf927BhAZMmTx1WuLE4cOHDhxnX2/HmcuEePGDAIcBoAAAECFihT\nNg52bNmwAdS2fXtcbt27ee8WJ64bOHDjiBOfNk2AAADLlwcgQGDHDmvWxIULBwB7du3juHf3/v3R\nowABAJQPEGDDhipq1Ny4kSHDljlzOHBIECNGtmzj+PMHABCAwIEECxo8iDChQoXjGjp8CDGiw1ix\nAFi0uGDBJmPGsmXzNi6kSJEASpo8OS5lSnHiwIELNy6mzJnjxD16xIBBgJ0AAAgQsECZsnFEixol\nCiCp0qXjmjp9CvWpOHHdwIEbhxXrtGkCBAD4+jUAAQI7dlizJi5cOABs27odBzf/rty5jx4FCAAg\nb4AAGzZUUaPmxo0MGbbMmcOBQ4IYMbJlGwcZMoDJlCtbvow5s+bNnMd5/gw6tOhx06YFCABgwAAj\nRsSJGwc7tuzY4sQBuI0797jdvMeJExdunHDh4sSFC0eDRoUBAwwYICBAQIAAAgQEGDRIm7Zx3Lt7\nBwA+vPhx5MubP0/+27cFCw6MGNGtm7dr1yZMAIB/wIAAAQoIAChgwoROncQdBJBQ4cJxDR06FDdu\nnDdvthIkAJBRIwABAgIIEAACBDVq2qRJAwHiggoV4cKNgwkTwEyaNW3exJlT506e43z+BBpUaC8A\nRQEMePZMnLhxTZ0+bSpOXLhu/90AXMWaddzWreDAiRM3TuxYXboQIAgQwIAGDSdOYKlThwEDAHUN\nGcKGbdxevn0B/AUceNzgweHCjUOcGDE4cAsWAACAIEMGDBhwFCgAQDMAHN++SZOWqVAhXbqcOQsn\nThwA1q1dixP3jRs3YLWBWaJB48QJBAB8/x4QIAAAAAMWLIgSRZmyb86cuXABAROmcdWrixMHQPt2\n7t29fwcfXvz4ceXNn0efvhcA9gAGPHsmTtw4+vXt0xcnLly3bgD8AwQgcCCAcQYNggMnTty4hg51\n6UKAIEAAAxo0nDiBpU4dBgwAgDRkCBu2cSZPogSgciXLcS5dhgs3bibNmeDALf9YAAAAggwZMGDA\nUaAAgKIAcHz7Jk1apkKFdOly5iycOHEArmLNKk7cN27cgIEFZokGjRMnEABIq3ZAgAAAAAxYsCBK\nFGXKvjlz5sIFBEyYxgEGLE4cgMKGDyNOrHgx48aOx0GOLHmyZFy4AGDGzAccuHGeP4P2LG706HHj\nAKBOrXoc69auXYcbMgQAAAECMFy61G03LVoNGgAITorUuOLGjxcHoHw583HOn0OHLk6LFgAABAhw\nIEIEAQIAvn8XICDbuPLmx4kTN279egDu38MHB+4bN26xYilQAGA//wABAA4YsGABgQABAAAQIELE\no0fRou2yYGHBghLfvokTN47/I0cAH0GGFDmSZEmTJ1GOU7mSZUuWuHABkCmTDzhw43Dm1IlTXM+e\n48YBEDqU6DijR5EiDTdkCAAAAgRguHSpW1VatBo0ALCVFKlxX8GG/QqAbFmz49CmVatWnBYtAAAI\nEOBAhAgCBADkzStAQLZxfwGPEyduXOHCABAnVgwO3Ddu3GLFUqAAQGXLAQIMGLBgAYEAAQAAECBC\nxKNH0aLtsmBhwYIS376JEzeONm0At3Hn1r2bd2/fv4GPEz6ceHHiFy4EcOAAGLBxz6FHlz59HADr\n17GP076de/czZzx4WLQo3Djz5sOF69LlwIER4+DHly8fQH3798fl179/vzhC/wAJ6dAxatQ3Z86g\nQAHAkAABX77GSZxIsSKAixgzitsYLlymTBQoCBiZIAEIQoSYMbNmbRoSJF683OHGTZs2aNBAaNBw\n5864n0CDAhhKtKjRo0iTKl3KdJzTp1CjQr1wIYADB8CAjdvKtavXr+MAiB1LdpzZs2jTnjnjwcOi\nReHGyZUbLlyXLgcOjBjHt69fvwACCx48rrDhw4fFESKkQ8eoUd+cOYMCBYBlAgR8+RrHubPnzwBC\nix4trnS4cJkyUaAgoHWCBCAIEWLGzJq1aUiQePFyhxs3bdqgQQOhQcOdO+OSK18OoLnz59CjS59O\nvbr1cdiza9+OHQuWAAGyfP/7Nq58+WvXxqlfz769egDw48sfR7++/fu4cOXK1a3bOIDjBA4cFyXK\ngAEexi1k2LAhAIgRJY6jWNGiRW2WLC1bxo2bOG3aLlwAMGAADhzjVK5k2VIlAJgxZY6jSfPbN1q0\najRqdOaMIleuvn3bti3bnDmSJL3JlcuGjQEDADRo4M3bOKxZtQLg2tXrV7BhxY4lW3bc2bPixI1j\n25YtLFgBAggQIG7c3bvBgpUogQCBiTJl1KjZNs7w4cMAFC9mPM7xY8iQxS1aFCzYt2/ixm3e/O3b\niRMAAMAZV9r06dMAVK9mPc71a9iwvw0bduxYt27aypQJ0PvVq2/fxg0nXtz/+HAAyZUvH9fc+fNw\n4b59k1aokBo1tmxFU6WqRIkZPHgMGADAPDBg49SvZ68ewHv48eXPp1/f/n384/TrFyduHMBxAgeO\ngwUrQAABAsSNa9gwWLASJRAgMFGmjBo128Zx7NgRAMiQIseRLGnSpLhFi4IF+/ZN3LiYMb99O3EC\nAAA443by7NkTANCgQscRLWrU6Ldhw44d69ZNW5kyAaa+evXt27isWrdyzQrgK9iw48aSLRsu3Ldv\n0goVUqPGlq1oqlSVKDGDB48BAwDwBQZsHODAggEDKGz4MOLEihczbux4HOTIkiWvGjAAAIAxY8Zx\n7mzIUIIEAEaPPnAA27jU/6pVA2jt+vW42LJn0+bG7dq1bt2yiRM37jc2bBIkAADAZhzy5MqVA2ju\n/Pm46NKnU//2DRkyT54sCBAAAICFb9/GkS9v/rx5AOrXsx/n/j18+LckSEiQgAWLJyxYIECgAGCA\nAAAIAgjAjds4hQsZKgTwEGJEiRMpVrR4EeM4jRs5clw1YAAAAGPGjDN50pChBAkAtGx54AC2cTNp\n0gRwE2fOcTt59vTJjdu1a926ZRMnblxSbNgkSAAAgM04qVOpUgVwFWvWcVu5dvX67RsyZJ48WRAg\nAAAAC9++jXP7Fm5cuADo1rU7Dm9evXpvSZCQIAELFk9YsECAQEGAAAAYA/8IwI3bOMmTKUsGcBlz\nZs2bOXf2/Bn0ONGjSYsWJ45PgQIePIxz/Rq2Jk0SJAAIEAAKFHDjePfuDQB4cOHjiBc3fhw58nDh\nQIAgQKDbOOnTqVMHcB179nHbuXf3Lk4cM2YaNBAAAIAAAWDj2Ld3/x4+APnz6Y+zfx8//mMLFhAg\nALBBAyYgCoI4QIAAgIUAMogTNy6ixIkRAVi8iDGjxo0cO3r8OC6kyJEhK1UiECBAmTLjWrp8KU5c\njRoAai5YEG6czp07Afj8CXSc0KFEixo1eu1agAACBFAbBzWqVKkAqlq9Oi6rVq3ixnkdJ44WrQcP\nAJgNEGDEiFXixIEDNy7/rty5dOMCuIs377i9fPv2zQEgMIAAAQgMGIAAAQEBAgIEECDAwbBhzpyN\nu4w5M4DNnDt7/gw6tOjRpMeZPo3adKVKBAIEKFNmnOzZtMWJq1EDgO4FC8KN+w0cOIDhxIuPO448\nufLly69dCxBAgABq46pbv34dgPbt3Md5//5d3Ljx48TRovXgAYD1AQKMGLFKnDhw4MbZv48/v30A\n/Pv7BzhO4ECCBHMAQAggQAACAwYgQEBAgIAAAQQIcDBsmDNn4zx+BAlA5EiSJU2eRJlS5cpxLV2+\n7NbNlKkEAQJYsTJO506e4MANGBBAgwYlSryNQ5o0KQCmTZ2Ogxp1nDiq/+OsXsWa1SoMGAAALFgw\nTuxYsmUBnEWbdtw4cePGgQMXTq44uuKUoUARIAAAAAYYMPjwAU6rVrJkefM2TvFixo0BPIYcOVw4\nceMsXx4XLtyuXQcAfAZQoICCAgUGDFBQocKCBQ0aWLhxgwEDDLdujcOdexwA3r19/wYeXPhw4sXH\nHUeevFs3U6YSBAhgxco46tWtgwM3YEAADRqUKPE2Tvz48QDMn0c/Tv36ceLcj4MfX/58+DBgAACw\nYME4/v39AxwnUCCAggYPjhsnbtw4cODCQRQnUZwyFCgCBAAAwAADBh8+wGnVSpYsb97GoUypciWA\nli5fhgsnbhzNmuPChf/btesAgJ4AChRQUKDAgAEKKlRYsKBBAws3bjBggOHWrXFWr44DoHUr165e\nv4INK3bsuLJmz3rzxoSJAAIElCgBB24c3brixO3ZA2AvBAiwYIkbJ3jwYACGDyMep3gx48aOG4MT\nIAAAAEGCxmHOrHkzgM6eP48LLXqcOHHcwIHTpu3LggUBAixYkCNMmB49LliwQIZMuHDjfgMPLhwA\n8eLGw4UTp3zcOHHisEmRIkGCgOoDBihQIAAAdwAHECBo0KBDBwsCBABIT4PGtm3j3r8HIH8+/fr2\n7+PPr3//uP7+AY4TOM6bNyZMBBAgoEQJOHDjIEYUJ27PHgAXIUCABUv/3DiPHz8CEDmS5DiTJ1Gm\nVJkSnAABAAAIEjSOZk2bNwHk1LlzXE+f48SJ4wYOnDZtXxYsCBBgwYIcYcL06HHBggUyZMKFG7eV\na1evAMCGFRsunDiz48aJE4dNihQJEgTEHTBAgQIBAPACOIAAQYMGHTpYECAAQGEaNLZtG7d4MQDH\njyFHljyZcmXLl8dl1rw5szRpSypUwIOHF69x4MB16xYJCBACBAYMiCNO3Djbt3HbBrCbd+9xv4EH\nFz5cuCkLFqJEGbeceXPnywFElz59XHXr16+LixULFqxr18Rp01asmJBEiZYtG7eefXv36wHElz9f\nnLhw48Z58/btWy0r/wCtdOgQ6datZcu0aXvWqBEwYNe2bdOmTZy4b8OGwYBxJFmycSBDjgNAsqTJ\nkyhTqlzJsuW4lzBjvvz2bRABAgYMCBAwIEAAAECBBgiwaJG4cUiTKlUKoKnTp+OiSp1KlRmzbt3G\naRUnzpUrCZYshQs3rqzZs2jLAljLtu24t3Djyp0LV5w4TceOWbM2rq/fv4D7AhhMuPC4w4e9eYsV\n68KCxwuujZtMubLly94yhws3rrPncQBCix5NurTp06hTqx7HurVr1t++DSJAwIABAQIGBAgAoHfv\nAAEWLRI3rrjx48cBKF/OfJzz59CjM2PWrdu46+LEuXIlwZKlcOHGif8fT768eADo06sfx769+/fw\n24sTp+nYMWvWxunfz7+/foAABA4kOM6gQW/eYsW6sMDhgmvjJE6kWNGiN4zhwo3j2HEcAJAhRY4k\nWdLkSZQpx61k2XIlOHClJkwAUNPmzTRpVKka19PnT6A9AQwlWnTcUaRJk7IiQeLDhz59VG3ZkiBB\ninFZtW7l2hXAV7Bhx40lW9bsWbPWwIHLlg0cuHFx5c6lC8DuXbzj9OrNlm3PHhQCBIQJM87wYcSJ\nFS9WDMDxY8iRJU+mXNny5XGZNW/ODA5cqQkTAIwmXTpNGlWqxq1m3dr1agCxZc8eV9v27dusSJD4\n8KFPH1VbtiRIkGL/3HHkyZUvB9Dc+fNx0aVPp16dujVw4LJlAwdu3Hfw4cUDIF/e/Dj06LNl27MH\nhQABYcKMo1/f/n38+fED4N/fP0AAAgcSLGjwIMKECgGMa+jw4UNoXryECIECRYEAAXbsmDbuI8iQ\nIkcCKGny5LiUKleuBNWgwYABJ04kOHAgQgRv43by7OnzJ4CgQoeOK2r0KNKkSsd9+yZO3LioUqdS\nBWD1KtZxWrWKE2fIUIIBA5w5G2f2LNq0ateqBeD2Ldy4cufSrWv37ri8evfy7ev3L2C9AAYTLjzu\nMOLEirt1y5bNmzdtzZpp0zbuMubMmjePA+D5M+hxokeTLm369Dhx/+LGsW7t+jVrALJn0x5n+/Zt\na+DAjevt+zfw4MKHAyhu/Djy5MqXM2/ufBz06NKnU69u/Xp0ANq3cx/n/Tv48N26ZcvmzZu2Zs20\naRvn/j38+PLHAahv//64/Pr38+/vH+A4ceLGFTR4EGFBAAsZNhz3ECJEa+DAjbN4EWNGjRs5AvD4\nEWRIkSNJljR5clxKlStZtnT5EqZKADNp1hx3E2dOnTt59vSJE0BQoUPHFTV6FGlSpUuZGgXwFGrU\ncVOpVrV6FWtWrVQBdPX6FWxYsWPJljU7Dm1atWvZtnX7Ni0AuXPpjrN7F29evXv59r0LAHBgweMI\nFzZ8GHFixYsLA/9w/BjyOMmTKVe2fBlz5skAOHf2/Bl0aNGjSZcedxp1atWrWbd2jRpAbNmzx9W2\nfRt3bt27edsG8Bt48HHDiRc3fhx5cuXEATR3/nxcdOnTqVe3fh27dADbuXf3/h18ePHjyY8zfx59\nevXr2bc/DwB+fPnj6Ne3fx9/fv376wPwDxCAwIEAxhk8iDChwoUMGx4EADGixHEUK1q8iDGjxo0V\nAXj8CDKkyJEkS5o8OS6lypUsW7p8CVMlgJk0a467iTOnzp08e/rECSCo0KHjiho9ijSp0qVMjQJ4\nCjXquKlUq1q9ijWrVqoAunr9Cjas2LFky5odhzat2rVs27p9mxb/gNy5dMfZvYs3r969fPveBQA4\nsOBxhAsbPow4seLFhQE4fgx5nOTJlCtbvow582QAnDt7/gw6tOjRpEuPO406terVrFu7Rg0gtuzZ\n42rbvo07t+7dvG0D+A08+LjhxIsbP448uXLiAJo7fz4uuvTp1Ktbv45dOoDt3Lt7/w4+vPjx5Mub\nP48+vfr17Nu7fw8/vvz59Ovbv48/v/79/Pv7BwhA4ECCBQ0eRJhQ4UKGDR0+hBhR4kSKFS1exJhR\n40aOHT1+BBlS5EiSJU2eRJlS5UqWLV2+hBlT5kyaNW3exJlT506ePX0KFCdu3FCiRY0ePSpO3Lhx\n4sY9hRo1KgCq/1WtisM6TutWrlrDhRMXVtw4smXNkhWXNu04tm3dAoAbV+44unXt3sWbd5w4ceP8\n/gUc2C8AwoUNhwsnbtxixuLGPX4sTtw4ypUtUw4XTtxmcOC6dfMGDtw40qXHAUCdWvVq1q1dv4Yd\ne9xs2rVt38adWzdtAL19/x4XXPhw4uLEjUOeXPly5s2TA4AeXfo46tWtX8eeXfv26gC8fwc/Tvx4\n8uXNnx8vTtw49uDcixM3Tv78cQDs38efX/9+/v39AwQgcCBBAOMOIkyocCHDhg4RAogoceK4ihYv\nYsyocSNHiwA+ggw5biTJkiZPokypkiSAli5fjospcybNmjZriv8TFy4cuHE+f/4EIHQo0aJGjyJN\nqnTpuKZOn0KNKnUqVacArmLNOm4r165ev4INK5YrgLJmz45Lq3Yt27Zu38JVC2Au3brj7uLNq3cv\n373ixIULB24c4cKFASBOrHgx48aOH0OOPG4y5cqWL48TJ24c586eP4PuDGA06dLjTqNOrXo1a9Xi\nxsGOLVs2gNq2b4/LrXs3796+fwPXDWA48eLjjiNPrnw583HixI2LHj1cOHHjrmPHDmA79+7ev4MP\nL348+XHmz6NPr36cOHHj3sOPL38+fAD27+Mfp38///7+AY4TOJCgQHHjECZUqBBAQ4cPx0WUOJFi\nRYsXMUoEsJH/Y8dxH0GGFDmS5Dhx4salTBkunLhxL2HCBDCTZk2bN3Hm1LmT5zifP4EG9QkOXJAg\nAgAAqFBBU7hw46BGlTpVKgCrV7GO07qVa1evX8dNm9anz6hxZ9GmTQuAbVu34+DGlTs3W7Zw4bx5\nEzdtGjhw0wAjQzaOcGHDhwkDULyY8TjHjyFDFtepkyRJhw4BK1Vq2rRRnTrt2jWONOlv38J9+zaO\ndetxAGDHlj2bdm3bt3HnHrebd2/fu8GBCxJEAAAAFSpoChduXHPnz6E/BzCdevVx17Fn176d+7hp\n0/r0GTWOfHnz5gGkV79+XHv37+FnyxYunDdv4qZNAwduWn9k/wCRjRtIsKDBgQASKlw4rqHDhw/F\ndeokSdKhQ8BKlZo2bVSnTrt2jRs58tu3cN++jVvJchyAlzBjypxJs6bNmzjH6dzJU5w4cOBoVagA\noKhRowKCBDFlChu2cVDDhQMnTty4q1jHAdjKteu4r2DDih07Nls2BQoAANggTty4t3DjvgVAt67d\ncXjz6sUrTlydBQsKFFiwAIICBQQIPDhyxIqVadPGSZ5MuTKAy5gzj9u8OVy4cePAiRMHDtymCxcS\nJIAAAQMCBAIEBFiwIFAgceLG6dYtLly4ccCDjwNAvLjx48iTK1/OvPm459DHiRMHrVOnKlUCANjO\nvXv3AQOsWP8BFy5ctGiZgAFLlkycuHHwAcifT3+c/fv484sT581bOIDhxg0c+A0BAgAJAWAY19Dh\nw4cAJE6kOM7ixYviunULFmwAAJAABAgIULLkgAIFFCjYtGncS5gxZQKgWdPmOJw5x4kT582nNGkX\nBAg4cCBECAYCBABgSoDArVvjpE6lWlUqAKxZtW7l2tXrV7Bhx40lO06cOGidOlWpEgDAW7hx4w4Y\nYMUKuHDhokXLBAxYsmTixI0jDMDwYcTjFC9m3FicOG/ewoUbV7nyNwQIAGwGgGHcZ9ChQwMgXdr0\nONSpU4vr1i1YsAEAZAMQICDA7dsDChRQoGDTpnHBhQ8nDsD/+HHk45QvHydOnDfo0qRdECDgwIEQ\nIRgIEADAOwECt26NI1/e/HnyANSvZ9/e/Xv48eXPH1ffvn1w374ZM4ZJCUAldOhw4ZLFhIkFCwAI\nENChgzJl4yZSRIaMGbNw4cZxBODxI8hxIkeSLAkOXLdu4cKNa9mymwQJAAAECBBtHM6cOnUC6Onz\n57igQocGDRdOFxcutWphw+btqTJlesiQ0aFj2LBxWrdy7QrgK9iw48aSLVvWmzVr49aOE3foEAIE\nAQoVGmf3Lt68eAHw7ev3L+DAggcTLjzuMGLE4L59M2YMkxIldOhw4ZLFhIkFCwAIENChgzJl40aT\nRoaMGbNw/+HGsQbg+jXscbJn064NDly3buHCjevdu5sECQAABAgQbRzy5MqVA2ju/Pm46NKnRw8X\nThcXLrVqYcPm7bsyZXrIkNGhY9iwcerXs28P4D38+OPm069f35s1a+P2jxN3COAhBAgCFCo0DmFC\nhQsVAnD4EGJEiRMpVrR4cVxGjRrFjfP4EaTHadNYsABQoMCZM+NYtnS5bRs4cONoArB5E+c4nTt5\n9vTmLVkyb97GFS2aK0AAAAAQIAA3DmpUqVIBVLV6dVxWrVu5duXqDRGiAQO+fBl3Fm1atQDYtnU7\nDm5cuXPpjgsXToOGAdq0jfP7F3BgwAAIFzZ8GHFixYsZN/8e9xhyZMmTx3XrRoBAgFu3woUb9xl0\n6M/hwokzDQB1atXjWLMWJ25cbNmxrVm7dm1cbt3jrADwDeDHj3HDiRc3DgB5cuXjmDd3/hz6c3Fm\nzAwYoECBuHHbuXfvDgB8ePHjyJc3fx79OHHiFCiAIE7cOPnz6denDwB/fv37+ff3DxCAwIEECxo8\nKHCcwoUMGzoc160bAQIBbt0KF26cxo0cNYYLJy4kgJEkS447eVKcuHEsW7K0Zu3atXE0a46zAiAn\ngB8/xvn8CTQogKFEi447ijSp0qVKxZkxM2CAAgXixlm9ihUrgK1cu477Cjas2LHjxIlToACCOHHj\n2rp9C/f/LYC5dOvavYs3r969fMf5/Qs4sOBxUqQAAKAAHLhxjBs7fuwYgOTJlMdZvow5c7du4zp7\nHidOnAEAAAYMuHZtnOrVrFsDeA079rjZtGvbvm1bXIkSBAgkSCBunPDhxIkDOI48+bjlzJs7fz4u\nXLgDB1qMu449u/btALp7/w4+vPjx5MubH4c+vfr17MdJkQIAgAJw4MbZv48/P34A/Pv7BzhO4ECC\nBbt1G5dQ4Thx4gwAADBgwLVr4yxexJgRwEaOHcd9BBlS5EiR4kqUIEAgQQJx41y+hAkTwEyaNcfd\nxJlT585x4cIdONBi3FCiRY0eBZBU6VKmTZ0+hRpV6jiq/1WtXsU6SoCAAAEmjQMbVuxYsgDMnkU7\nTu1atm21aRsXV+64Zs0MNGhAi9Y4vn39/uULQPBgwuMMH0acWHFib44cgQBx5sw4ypUtXwaQWfPm\ncZ09fwYdepwyZSVK7BqXWvVq1q0BvIYdW/Zs2rVt38Y9Tvdu3r19jxIgIECASeOMH0eeXDkA5s2d\nj4MeXfp0bdrGXcc+rlkzAw0a0KI1Tvx48uXFA0CfXv049u3dv4f/3psjRyBAnDkzTv9+/v0BAAQg\ncODAcQYPIkyocJwyZSVK7BoncSLFihYBYMyocSPHjh4/ggw5biTJkiZHKlPGgEEAAAASJIg0bibN\nmjZvAv/IqXPnuJ4+fYobN06btlWVKoULN26pNGkNGgSwYuXbt3FWr2LNahUA165ex4ENK3YsWXHV\nqmnTNixUKA0aLl0SN24u3bp1AeDNq3cc375+/4oTN26cOHHbZMigQMHRuMaOH0OODGAy5cqWL2PO\nrHkz53GeP38ON270uGxAgAwYAADAAAIEJEi4oUYNHjy2bI3LrXs3bwC+fwMfJ3z4cHDEiPnxs6JE\nCUmSXLkyZMAAAAADkiUbp3079+7cAYAPL34c+fLmz2/bRovWnTtHWrQAA6ZTrFg0aJAggW0c//7+\nAY4TOA5AQYMHx40TN25cuHDjxokbN06btlqFCtWqVar/1CMECA4c2PLtmzhx4cKNU7mSZUsAL2HG\nlDmTZk2bN3GO07lzZ7hxP8dlAwJkwAAAAAYQICBBwg01avDgsWVrXFWrV7EC0LqV6zivX7+CI0bM\nj58VJUpIkuTKlSEDBgAAGJAs2Ti7d/HmxQuAb1+/4wAHFjx42zZatO7cOdKiBRgwnWLFokGDBAls\n4zBn1qwZQGfPn8eNEzduXLhw48aJGzdOm7ZahQrVqlWq1CMECA4c2PLtmzhx4cKNEz6ceHEAx5En\nV76ceXPnz6GPkz59ujjr27bdGDAAAIACBVSwYcOFCwEA5wEIEBBtXHv3798DkD+f/jj79+9jGzRI\ng4YG/wAVKECA4MABAAgRLmjUyJu3cRAjSpwIEYDFixjHadzIsWO1ak6cwICBIEOGI0c8hQmDAQMD\nBoXGyZxJkyaAmzhzjtvJs+dObtzicOAwYwYTJiMMGBAgQAMxYt68adPGrSq4q+OyatUKoKvXr2DD\nih1LtqzZcWjTphXHdtu2GwMGAABQoIAKNmy4cCEAoC8AAQKijRtMuHBhAIgTKx7HuHFjbIMGadDQ\nQIECBAgOHADAmfOCRo28eRtHurTp06QBqF7Nepzr17BjV6vmxAkMGAgyZDhyxFOYMBgwMGBQaJzx\n48iRA1jOvPm459CjP+fGLQ4HDjNmMGEywoABAQI0EP8j5s2bNm3c0oNbP669e/cA4sufT7++/fv4\n8+sfx7+/f4DjxokTN2rCBAsWSJH6xo0bNGgnBgwIEKBAAWTjNG7kyBHAR5Ahx40kSRLbsGGAAM05\ncmTNGjJkQN26RYsWtm05t43j2dPnT54AhA4lOs7oUaRJjYZjGg7ct2/hwnlDhgwLliNHqo3j2tWr\nVwBhxY4dV9bs2bPgevVKlgwZskgLFjRoYEWcuHF59Y4LF27cX8CBAQwmXNjwYcSJFS9mPM7xY8iQ\ngdmwAQZMsWLhuHHbtk3Ojh0LFnjwcAUcuHDhxq1m3RrAa9ixx82mTZvbbWTIdh07xs03t3HBhfPi\n1aD/gSZN45QvZ94cwHPo0cdNp17d+vXry5ahQOHDR7Fx4cWPHw/A/Hn049SvZ99enLhx46hR6zJg\ngAEDccbt59/fP8BxAgcCKGjwIMKEChcybOhwHMSIEiUCs2EDDJhixcJx47Ztm5wdOxYs8ODhCjhw\n4cKNa+nyJYCYMmeOq2nTJrecyJDtOnaMG1Bu44YS5cWrQQNNmsYxber0KYCoUqeOq2r1KtasWZct\nQ4HCh49i48aSLVsWANq0asexbev2rThx48ZRo9ZlwAADBuKM6+v3L+DAAAYTLmz4MOLEihczHuf4\nMWTI3NiwuXLl1i1qxYq5coUNHLhbt4oUyYAHz5Il/8TGsW7dGgDs2LLH0a5dOxw3buB2U6O2bdu4\n4MKDT5pEgAACBNjGMW/u3DmA6NKnjxsn7nq4cOO2c+/u3bs3b0eOdOiQbRz69OrVA2jv/v24+PLn\n058vTlysBg0ePDg2DuA4gQMJFiwIAGFChQsZNnT4EGLEcRMpVqzIjQ2bK1du3aJWrJgrV9jAgbt1\nq0iRDHjwLFlCbFxMmTIB1LR5c1xOnTrDceMGDig1atu2jTN61OikSQQIIECAbVxUqVOnArB6Feu4\nceK4hgs3DmxYsWPHevN25EiHDtnGtXX79i0AuXPpjrN7F29evOLExWrQ4MGDY+MIFzZ8GDEAxYsZ\nN/92/BhyZMmTx1W2fPlyOFGiUqTgwMHDlSumTIUbNw4cOD58AgAAIEBAknGzadMGcBt37nG7efMW\n9ztcuG2yZE2bNg55cuQePAQIIECAs3HTqVevDgB7du3juHcfJw78OPHjyZcXDw5cnz4WLFQb9x5+\n/PgA6Ne3Pw5/fv37+dOCARCGFCnexhk8iDChQgAMGzp8CDGixIkUK467iDFjxnCiRKVIwYGDhytX\nTJkKN24cOHB8+AQAAECAgCTjatq0CSCnzp3jevr0KS5ouHDbZMmaNm2c0qVKPXgIEECAAGfjqlq9\nehWA1q1cx3n9Ok6c2HFky5o9SxYcuD59LFioNi7/rty5cwHYvYt3nN69fPv6pQUDhhQp3sYZPow4\nsWIAjBs7fgw5suTJlCuPu4w5s+Zo0axYWbAAw5EjyZKJGzcOHLhLlwIAAECAQKBxtGvXBoA7t+5x\nvHv7/s2K1bJl44obH+dNgwYBAkqUEDcuuvTp0wFYv459nPbt28WN+w4+vPjv4sSBAkWECLhx7Nu7\ndw8gvvz54+rbv48/P7hQoZYtA9ht3Lhw4cCBG5dQ4UKGABw+hBhR4kSKFS1eHJdR40aO3ryRIbNA\nJAgQf/7sypZt0CABAgC8NGBgljhx42zeHAdA506e43z+BBqUBAkDBlCgqObMGTBgEAYMECDg0aNx\n/1WtXsUKQOtWruO8fv0qbtxYsmXNjqtGh06BAggQ2BoXV+7cuQDs3sU7Tu9evnrFiRsnTrA4Y8Yo\nOXDQocOGKVNQoIABo9k4ypUtWwaQWfNmzp09fwYdWvQ40qVNn/bmjQyZBa1BgPjzZ1e2bIMGCRAA\nQLcBA7PEiRsXXPg4AMWNHx+XXPly5iRIGDCAAkU1Z86AAYMwYIAAAY8ejQMfXvx4AOXNnx+XXr16\ncePcv4cff1w1OnQKFECAwNY4/v39AxwncByAggYPjkuocGFCceLGiYsozpgxSg4cdOiwYcoUFChg\nwGg2biTJkiUBoEypciXLli5fwow5bpy4cePEif8bp3OnznDhWrWaM4dXo0ZcuBi6cmXBAgBOr1y5\ndavbuKpWrQLIqnXruK5ev4ItVChAgAEDEChQEGBtihRVqhw7Nm4u3bp2AeDNq3cc375+xYnbtm1Z\nrFjFinHjlu3WLRQoLBQoECCAAAHRxmHOrFkzgM6eP48LLVp0OGzYmDE7FipUjBggQBgYMAAAAAEI\nECxYIEGCtHG+fwMHDmA48eLGjyNPrnw583HjxI0bJ07cuOrWq4cL16rVnDm8GjXiwsXQlSsLFgBI\nf+XKrVvdxsGPHx8A/fr2x+HPr39/oUIBAAYYMACBAgUBEKZIUaXKsWPjIEaUOBFARYsXx2XUuFH/\nnLht25bFilWsGDdu2W7dQoHCQoECAQIIEBBtXE2bN28C0LmT5zifP3+Gw4aNGbNjoULFiAEChIEB\nAwAAEIAAwYIFEiRIG7eVa9euAMCGFTuWbFmzZ9GmHTdO3Di3b+HCFTdX3DdevEqVIsOAgQABAQLs\n4MZNnLhxhxEnBrCYceNxjyFHlpwtW5cuOHAQECDAgIEcq1bt2jWOdGnTp0kDUL2a9TjXr2G7Ficu\njgcPChS4cHFgwAAAAAgECECAABMm45AnV74cQHPnz8dFly4dXLdu2bJJevGiQPcCAMALEOChRAkQ\nIHbtGreefXv3AODHlz+ffn379/HnH7eff3///wDHCRxIkKA3b+LEjVvIsKFDABAjShxHsaLFixjH\nhdsYbpzHjyBDihwHoKTJk+NSqlzJEho0ZMisWZs2bJgwYd7ChRMnbpzPn0CD+gRAtKjRcUiTKl2K\ntFu3bdu4adMmTty4q1izat06DoDXr2DDih1LtqzZs+PSql3Ltq3bcd68iRM3rq7du3gB6N3Ld5zf\nv4ADCx4XrnC4cYgTK17MeByAx5Ajj5tMubJlaNCQIbNmbdqwYcKEeQsXTpy4cahTq16NGoDr17DH\nyZ5Nu7bsbt22beOmTZs4ceOCCx9OvPg4AMiTK1/OvLnz59Cjj5tOvbr169iza6cOoLv37+PCi/8f\nT768+fPoxQNYz779uPfw48ufT7++ffgA8uvfP66/f4DjBA4kWNDgQYQGASxk2NDhQ4gRJU6kOM7i\nRYwZNW7k2PEiAJAhRY4jWdLkSZQpVa4sCcDlS5jjZM6kWdPmTZw5ZwLg2dPnOKBBhQ4lWtTo0aAA\nlC5l2tTpU6hRpU4dV9XqVaxZtW7lahXAV7Bhx40lW9bsWbRp1ZIF0Nbt23Fx5c6lW9fuXbxyAezl\n23fcX8CBBQ8mXNgwYACJFS9m3NjxY8iRJY+jXNnyZcyZNW+uDMDzZ9DjRI8mXdr0adSpRwNg3dr1\nONixZc+mXdv27dgAdO/mPc73b+DBhQ8nXvz/NwDkyZUvZ97c+XPo0cdNp17d+nXs2bVTB9Dd+/dx\n4cWPJ1/e/Hn04gGsZ99+3Hv48eXPp1/fPnwA+fXvH9ffP8BxAgcSLGjwIEKDABYybOjwIcSIEidS\nHGfxIsaMGjdy7HgRAMiQIseRLGnyJMqUKleWBODyJcxxMmfSrGnzJs6cMwHw7OlzHNCgQocSLWr0\naFAASpcyber0KdSoUqeOq2r1KtasWrdytQrgK9iw48aSLWv2LNq0askCaOv27bi4cufSrWv3Ll65\nAPby7TvuL+DAggcTLmwYMIDEihczbuz4MeTIkidTrmz5MubMmjdz7uz5M+jQokeTLm36NOrU/6pX\ns27t+jXs2LJn065t+zbu3Lp38+7t+zfw4MKHEy9u/Djy5MqXM2/u/Dn06NKnbw4XTty47Nq3c+8u\nThy48N++iRM37jz69OoBsG/vXhz8cfLnixtnf5y4cOHE8Rc3DuA4gQMJEgQHblxChQsBNHT4MFy4\ncRMpVrR4EWPGjOLEAfD4EaQ4ceNIlhwnblzKlOHCiXMpbpw4ceHCddOmjRq1b9/EjRsnTtw4ceLG\nFS0qThwApUuZNnX6FGpUqVPHVbV6FWtWq968bQMHTpy4cWPJljU7FkBatWvHtXX7Fq44cePo1rV7\nF2/eugD49vU7DnBgwYMJFzZ8ODAAxYsZj/9z/BhyZMmPv33jtm1btmzixI3z/Bl0aACjSZc2fRp1\natWrWY9z/Rp2bNmvu3XbFi7cON27effmDQB4cOHjiBc3fhx5cuTduo1z/hy6cwDTqVcfdx17du3b\nuXf3jh1AePHjx5U3fx59evPgwG3Dhi1cuHHz6de3Px9Afv37+ff3DxCAwIEECxo8iFDguIUMGzp8\nyLBbt23hwo27iDGjxowAOnr8OC6kyJEkS5os2a3buJUsW64EADOmzHE0a9q8iTOnzp01Afj8CXSc\n0KFEixodCg7cNmzYwoUbBzWq1KlQAVi9ijWr1q1cu3r9Oi6s2LFky44TJw4btm/j2rp9Czf/LoC5\ndOuOu4s3r969fPVqAwdunODBhAUDOIw48bjFjBs7fgw5smTGACpbvjwus+bNnDtrvnatmThx40qb\nPo36NIDVrFu7fg07tuzZtMfZvo07t+5x4sRhw/ZtnPDhxIsbB4A8ufJxzJs7fw49+nNt4MCNu449\n+3UA3Lt7Hwc+vPjx5MubPx8egPr17Me5fw8/vvz31641EydunP79/PvzBwhA4ECCBQ0eRJhQ4cJx\nDR0+hBhRHClSYcLQGpdR40aOHQF8BBly3EiSJU2eRDkuUaIFCwAgQMCM2TiaNW0CwJlT5ziePceF\nCydu3FCiRceF69FDly5T4MCNgxpV6lSp/wCsXsU6TutWrl29jqNECQyYTOHCjUObVu1atQDcvoUb\nV+5cunXt3h2XV+9evn3FkSIVJgytcYUNH0acGMBixo3HPYYcWfJkyuMSJVqwAAACBMyYjQMdWjQA\n0qVNj0Odely4cOLGvYYde1y4Hj106TIFDtw43r19//YNQPhw4uOMH0eeXPk4SpTAgMkULtw46tWt\nX7cOQPt27t29fwcfXvz4ceXNn0d/Xpy4Uw4cHDggJ1y4cfXtjwsXjtu2beDAARQnLpw4cQAOIkwo\nThy4cA7DjYsocSLFcOGwYdslQgSAjh0DBDh2bBzJkiYBoEypctw4cePGUaPWqxckN27UqP/xYcBA\ngAAAfgL9GSCACRPhwo1LqnQpUwBOn0IdJ3Uq1apSxYm7ds0LAgQKFEwaJ3Ys2bJmAaBNq3Yt27Zu\n38KNO24u3bp254YLZ8VKAAB+ATA4dIgWrWXLsHXrRoyYpVWOV3HjFm4ygMqWL4vLnHkc53HixoEO\nHRocuFc+fBAg4CBAAACuXwOABGkc7dq2AeDOrXscb3HiqlXz4WMAgOLGjyNPzonTuObOn0MHIH06\n9XHWr2PPbt2XrwcPAIAXIEDOuPLmz6NPD2A9+/bu38OPL38+/XH27+PPbz9cOCtWAAYAMBAAg0OH\naNFatgxbt27EiFlaNXEVN27hMALQuJH/oziPHseFHCduXEmTJsGBe+XDBwECDgIEADCTJgBIkMbl\n1LkTQE+fP8cFFSeuWjUfPgYAULqUaVOnnDiNkzqValUAV7FmHbeVa1evW335evAAQFkBAuSMU7uW\nbVu3AODGlTuXbl27d/HmHbeXb1+/e5UpGzAAQGEBAh5cukSGTJIkdooVGzYs2rFj27aJEzdOnDgA\nn0GHHjeadGnTpcOFQ7ZnjxUrpHLlihABQO0CBbp1G7ebd28Av4EHHzdOXHFevMiQSSBAAADnz58z\nWLOGBAkBALADAANmXHfv38EDED+e/Djz59GnN//njwABAQ4cqFGj2Dj79/Hn1w+Af3///wABCBxI\nsKDBgwgTKgQwrqHDhxAbKlM2YACAiwIEPLh0iQyZJEnsFCs2bFi0Y8e2bRMnbpw4cQBiypw5rqbN\nmzhvhguHbM8eK1ZI5coVIQKAowUKdOs2rqnTpwCiSp06bpy4q7x4kSGTQIAAAGDDhmWwZg0JEgIA\nqAUABsy4t3DjygVAt67dcXjz6t2L988fAQICHDhQo0axcYgTK17MGIDjx5AjS55MubLly+Mya97M\nuVYtAgQAiBYtQMAAAwYCBBAgYMGrV8eOhdu2LVy4cbhxA9jNu/e438CDCx/e7dmzccjBgRMhAoBz\nO3bChRtHvbp1ANizax/Hnbs3b8qUmf/RpcuFCw5ChDhzFi7cuPfvwW3YAACABQvj8uvfzx+Af4AA\nBA4EMM7gQYQJefEqUAAAgAERIihShGjXLi5cuHEb19HjR5AARI4kWdLkSZQpVa4c19Lly5fADhwA\nUNMmgAABBAwYAABAgAA2vg39Js7oOKRJxwFg2tTpOKhRpU6lOk6cuHDhxv36RYAAAAALxo0lW7Ys\nALRp1YpjO87tW7jjxHXrNs7uXbx79gAAMGDAOMCBBQ8GUNjwYXHiwokTFy7cOMiRx/UyYAAAAAIE\n8JAhI0VKCwkSCBCAAAHaONSpVasG0Nr1a9ixZc+mXdv2ONy5desGduAAAODBAQQIIGD/wAAAAAIE\nsPHN+Tdx0cdNpz4OwHXs2cdt597d+/dx4sSFCzfu1y8CBAAAWDDO/Xv48AHMp19f3P1x+fXvHyeu\nG8Bu4wYSLLhnDwAAAwaMa+jwIUQAEidSFCcunDhx4cKN6+hxXC8DBgAAIEAADxkyUqS0kCCBAAEI\nEKCNq2nz5k0AOnfy7OnzJ9CgQoeOK2r0aFFu3JAQIADgKYAABAgMGBAAAFYAAgTQEiduHNiwYsEC\nKGv27Li0ateybcsW24EDAOYCyDbuLt68eQHw7etXnLhxggcTHuzN27jEihenSAEAgAUL4yZTrmwZ\nAObMmsVx7tyZW7duggQNAABAgAA2/2ySDRvWqpUGALIBIEBwbRzu3Lp1A+jt+zfw4MKHEy9ufBzy\n5MqRc+OGhAABANIBBCBAYMCAAAC2AxAggJY4cePGky8/HgD69OrHsW/v/j3899gOHABgH0C2cfr3\n8+cPACAAgQMHihM3DmFChQm9eRv3EGLEFCkAALBgYVxGjRs5AvD4EaQ4kSNHcuvWTZCgAQAACBDA\nhk2yYcNatdIAACcABAiujfP5EyhQAEOJFjV6FGlSpUuZjnP6FCpUb7NmLVqUJw8lGzY6dBDw1YCB\nKVPGlTV7Fi0AtWvZjnP7Fm5cuXErmTCxYMG3b+P49vX7F0BgwYPHFTZ8GLE4ceMYN/92PGXKggXS\npI2zfBlzZgCbOXcW93ncOHDgxImrNm3aihUV3rzx5m1c7NjbtlV5cPuBK1fjePf2/RtAcOHDiRc3\nfhx5cuXjmDd37tzbrFmLFuXJQ8mGjQ4dBHQ3YGDKlHHjyZc3DwB9evXj2Ld3/x7++0omTCxY8O3b\nOP37+fcHABCAwIEDxxk8iDChOHHjGjp8OGXKggXSpI27iDGjRgAcO3oUB3LcOHDgxImrNm3aihUV\n3rzx5m2cTJnbtlV5gPOBK1fjevr8CRSA0KFEixo9ijSp0qXjmjp9CrWpOHG+fAmyYEGAAABcAwRQ\npWqc2LFkywI4izbtuLVs27p9yzb/ViwLvHiJEzcur969fPMC+As48LjBhAsXxqZBgytX4sSNe/zt\n24MDBxAgGIc5s+bNmAF4/gx63Dhx48aBA2fMWBMNGgIEaAAM2LjZtMdVq4aBAoUkScb5/g08uG8A\nxIsbP448ufLlzJuPew49uvTn1qxt2hSkQAEA3LkLEODEybjx5MubB4A+vfpx7Nu7fw9/HDJkCxac\nGYc/v/79/AH4BwhA4EAA4wweRGgwXDgnAwYECAABgosKFQQIANCjx6JF4cKNAxlS5EgAJU2eHJcy\npTdvvXrBCBAzwAllysbdvBkuXI8eCrJkuXVr3FCiRY0OBZBU6VKmTZ0+hRpV6jiq/1WtXqVqzdqm\nTUEKFAAQNqwAAU6cjEObVu1aAG3dvh0XV+5cunXHIUO2YMGZcX39/gUcGMBgwoXHHUac+HC4cE4G\nDAgQAAIEFxUqCBAAoEePRYvChRsXWvRo0gBMn0Y9TrVqb9569YIRQHaAE8qUjcONO1y4Hj0UZMly\n69Y44sWNHycOQPly5s2dP4ceXfr0cdWtX8cuTlysWCNGCAAQXrx4GjTGnUefXj0A9u3dj4MfX/58\n+uEwYAAAINo4/v39AxwncCDBcQAOIkw4biHDhgvDhRMRIAAAAAECAMiYMUCJEq5cjQspciTJkABO\nokw5biXLcdWqtQAgE0CAFCm8ef8bN45bkyYAfhow8OrVuKJGjyItCmAp06ZOn0KNKnUq1XFWr2LN\nKk5crFgjRggAIHbsWBo0xqFNq3YtgLZu346LK3cu3brhMGAAACDauL5+/wIODGAw4cLjDiNOfDhc\nOBEBAgAAECAAgMqVA5Qo4crVuM6eP4PuDGA06dLjTqMeV61aCwCuAQRIkcKbt3HjuDVpAmC3AQOv\nXo0LLnw48eAAjiNPrnw58+bOn0MfJ3069erbttWp48DBBQ0aWrTg4MCBAQNTpoxLr349ewDu38Mf\nJ38+/fr2t2nQ4MDBuP7+AY4TOJBgwXEAECZUOI5hQ4cOv+nQYcECAgQLDGQ0YGL/2bJt28aFFDmS\nZEgAJ1GmHLeS5Thx4lR9+CBAQAGbQoQkScIhQAAAP1OkqFZtXFGjR5EWBbCUaVOnT6FGlTqV6jir\nV7Fi9bZhw4ABBgxoKVYMHDhfOHAECECAQJtxb+HGjQuAbl274/Dm1btXrzZtRwQIkCCB2zjDhxEn\nVgyAcWPH4yBHljw5WTJYsKRIaXPihAQJFY4do0ZtXGnTp1GXBrCadetxr2HDDrds2YIFAHDn1h2A\nNwECBgyIEzeOeHHjxwEkV76ceXPnz6FHlz6OenXr1r1t2DBggAEDWooVAwfOFw4cAQIQINBmXHv3\n798DkD+f/jj79/Hnx69N2xEB/wAFSJDAbZzBgwgTKgTAsKHDcRAjSpyYLBksWFKktDlxQoKECseO\nUaM2rqTJkyhLAljJsuW4lzBhhlu2bMECADhz6gzAkwABAwbEiRtHtKjRowCSKl3KtKnTp1CjSh1H\ntapVq7cIEAgQAAMGY9++gQO3rU+fAwcAABjgypU3b+Piyp0LoK7du+PGeQsXzpu3cYADC/727csX\nEwsSL4g2rrHjx5AjA5hMufK4y5gza76cLVu1asyAAEGAYII1a968jVvNurXr1QBiy549rrbt2+HC\nHTvGAoBvAAECZDhypEoVCgoUBAigQcO459CjSwdAvbr169iza9/Ovfu47+DDh/+/RYBAgAAYMBj7\n9g0cuG19+hw4AADAAFeuvHkbx7+/f4AABA4kOG6ct3DhvHkb19Dhw2/fvnwxscDigmjjNG7k2NEj\nAJAhRY4jWdLkSZLZslWrxgwIEAQIJliz5s3bOJw5de7ECcDnT6DjhA4lGi7csWMsACwFECBAhiNH\nqlShoEBBgAAaNIzj2tXrVwBhxY4lW9bsWbRp1Y5j29atWyMBAgAAECGCpGTJiBHrlSGDAAEAAARo\n0CBVKnHjFC9eDMDxY8jjxokbN06cuHGZNWf+9k2ECBYsUpAhI0IEsXGpVa9m3RrAa9ixx82mXdv2\nbHHiwoXr5cEDAQIWunUbV9z/+HHkxwEsZ9583HPo0aMTmzChRo08ecSN4z4OlwABAAAIEIBt3Hn0\n6dMDYN/e/Xv48eXPp19/3H38+fMbCRAAAEAAESJISpaMGLFeGTIIEAAAQIAGDVKlEjfuIkaMADZy\n7DhunLhx48SJG2fypMlv30SIYMEiBRkyIkQQG2fzJs6cOgHw7OlzHNCgQocCFScuXLheHjwQIGCh\nW7dxUqdSrUoVANasWsdx7erVK7EJE2rUyJNH3Li043AJEAAAgAAB2MbRrWvXLoC8evfy7ev3L+DA\ngscRLmzYcLcSJTZsAALklixZ1aolY8WqQoUJEzI8enTt2rjQokcDKG369LjU/6pXs/7168kTYMC+\nWbN269a43Lp38+49DgDw4MLHES9u/DhycXjwyJAxaRz06NKnUwdg/Tr2cdq3c+/u3bsRIwECJEgA\nbhz69OrVA2jv/j38+PLn069vfxz+/Pr3e/PGDSC3bdu+adM2DiHCbNmwYeMVLpw4ceMoVrQIAGNG\njeM4dvTIcdkyNRQoePECDJg4a9acORv3EmZMmTPHAbB5E+c4nTt59vQ5zoKFAQM4jDN6FGlSpQCY\nNnU6DmpUqVOpUsWBAwCAAgWojfP6FSxYAGPJljV7Fm1atWvZjnP7Fm5cb964cdu27Zs2beP48s2W\nDRs2XuHCiRM3DnFixQAYN/92PA5yZMmQly1TQ4GCFy/AgImzZs2Zs3GjSZc2fXocANWrWY9z/Rp2\nbNnjLFgYMIDDON27eff2DQB4cOHjiBc3fhw5chw4AAAoUIDaOOnTqVMHcB17du3buXf3/h28OHHh\nxpU3fx59evXatH0b9x5+/PgA6Ne3Pw5/fv3ixDFjBrAJBw5o0HjzNq5aNVCgxjl8CDGixHEAKlq8\nOC6jxo0cO4pbsAAAgCPjSpo8iTIlgJUsW457CTOmzJkyv5EgESDAgAHbxvn8CRQogKFEixo9ijSp\n0qVMxYkLNy6q1KlUq1rVpu3buK1cu3YFADas2HFky5oVJ44ZsyYcOKBB483/27hq1UCBGoc3r969\nfMcB+As48LjBhAsbPixuwQIAAI6Meww5suTJACpbvjwus+bNnDtz/kaCRIAAAwZsG4c6tWrVAFq7\nfg07tuzZtGvbHoc7t+7dvHvvFjcuuPDhwwEYP458nPLlzJVXq2bnxo1bt759C0eNGjFi47p7/w4+\n/DgA5MubH4c+vfr17KcBeA8A0rj59Ovbvw8gv/794/r7BzhO4ECCBQtqQ4AgQAAJEraNgxhRokQA\nFS1exJhR40aOHT2OAxlS5EiSJU2eDAlA5UqW41y+hBnz2zdx4sbd7NYtXLhxPX3+BBp0HACiRY2O\nQ5pU6VKm0gIEaNBg3FSq/1WtXh0HQOtWruO8fgUbVqxYaNAkSerWbdxatm3dAoAbV+5cunXt3sWb\nd9xevn39/gUcWDBfAIUNHx6XWPFixt++iRM3TnK3buHCjcOcWfNmzuMAfAYdetxo0qVNn5YWIECD\nBuNcv4YdW/Y4ALVt3x6XW/du3r17Q4MmSVK3buOMH0eeHMBy5s2dP4ceXfp06uOsX8eeXft27t2v\nAwAfXvw48uXNn0efXv368gDcv4c/Tv58+vXth4sQoVq1cf39AxwncCDBguMAIEyocBzDhg4fQowo\ncWJDABYvYsyocSPHjh4/jgspciTJkiZPohQJYCXLluNewowpcybNmjZhAv/IqXPnuJ4+fwINGi5C\nhGrVxiFNqnQp03EAnkKNOm4q1apWr2LNqpUqgK5ev4INK3Ys2bJmx6FNq3Yt27ZpxYkbJ3cu3boA\n7uLNO24v375+/44TJ24c4cKGDyMuDGAx48bjHkOOLHmyOGvWxmHOrHkz58wAPoMOPW406dKmT6NO\nrZo0gNauX8OOLXs27dq2x+HOrXs37965xYkbJ3w48eIAjiNPPm458+bOn48TJ24c9erWr2OvDmA7\n9+7jvoMPL368OGvWxqFPr349+/QA3sOPP24+/fr27+PPr58+gP7+AQIQOJBgQYMHESZUWHBcQ4cP\nIUaUOJGiQwAXMWYct5H/Y0ePH0GGFMkRQEmTJ8elVLmSZUuXL2GqBDCTZs1xN3Hm1LmTZ0+fOAEE\nFTqUaFGjR5EmVTqOaVOnT6FGlTq1KQCrV7GO07qVa1evX8GG3QqAbFmz49CmVbuWbVu3b9MCkDuX\n7ji7d/Hm1buXb9+7AAAHFjyYcGHDhxEnHreYcWPHjyFHlswYQGXLl8dl1ryZc2fPn0FrBjCadOlx\np1GnVr2adWvXqAHElj17XG3bt3Hn1r2bt20Av4EHFz6ceHHjx5EnV76ceXPnz6FHlz6denXr17Fn\n176de3fv38GHFz+efHnz59GnV7+efXv37+HHlz+ffn379/Hn17+ff3//kwABCBxIsKDBgwgTKlzI\nsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fP\nn0CDCh1KtKjRo0iTKl3KtKnTp1CjSp1KtarVq1izat3KtavXr2DDih1LtqzZs2jTql3Ltq3bt3Dj\nyp1Lt67du3jz6t3Lt6/fv3wDAgAh+QQICgAAACwAAAAAIAEgAQAI/wABCBxIsKDBgwgTKlzIsKHD\nhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CD\nCh1KtKjRo0iTKl3KtKnTp1CjSp1KtarVq1izat3KtavXr2DDih1LtqzZs2jTql3Ltq3bt3Djyp1L\nt67du3jz6t3Lt6/fv4ADCx5MuLDhw4gTK17MuLHacuXMnZtMuZxly+fMmTvHubNnzuPGlStn7pzp\n06hRA1jNurU5c+diy55Nu7bt27jPAdjNu7c5c+eCCx9uzty548iPl1u+3Jzz5+eiS59OHYD169jL\nlTN3rrt3c+DBn/8bT768+fPoy5szB6C9+/fw48ufT7++/XP48+cvZ87cOYDnBA4kWNDgwYMAFC5k\neM7hQ4gRJU6kWPEhAIwZNZ7j2NHjR5DnzI0cec7kSZQpVQJg2dLlOZgxZc6kWdPmzZgAdO7k2dPn\nT6BBhQ49V9ToUaRJlZ4zZ+7cU6hRpT4FUNXq1XNZtW7l2tXrV7BaAYwlW/bcWbRp1a5l29YtWgBx\n5c49V9fuXbx59e7laxfAX8CBBQ8mXNjwYcTnFC9m3Njx43PmzJ2jXNnyZcoANG/mfM7zZ9ChRY8m\nXfozANSpVZ9j3dr1a9ixZc9uDcD2bdzndO/m3dv3b+DBdwMgXtz/+HHkyZUvZ9783HPo0MuZM3fO\n+nXs2b99K1fu3Hfw4cV/B1De/Plz6dWvZ9/e/Xv46gHMp1//3H38+fXv59/fP8BzAgEQLGjwHMKE\nChcyTEiOnLlzEidSrGgRAMaMGjdy7OjxI8iQ50aSJFnOnLlzKleybPntW7ly52bSrGlzJoCcOnee\n6+nzJ9CgQocS9QngKNKk55Yyber0KdSoUpkCqGr16rmsWrdy7aqVHDlz58aSLWv2LIC0ateybev2\nLdy4cs/RrVu3XLdu2bKBK1fOnLlxgrNlI0ZMECFC5syda+z4MeTGACZTrnzuMubMms2ZO3euXDlz\n50aTLm36tGkA/6pXsz7n+jVs2OXGjStXzpy5c7p38+7t2zeA4MKHnytu/Djy4+TIRdKgAQOGLseO\nmTN37jr27NqvA+ju/Tv48OLHky9v/hz69OnLdeuWLRu4cuXMmRtnP1s2YsQEESJkDqC5cwMJFjQ4\nEEBChQvPNXT4EKI5c+fOlStn7lxGjRs5duQIAGRIkedIljRpsty4ceXKmTN3DmZMmTNp0gRwE2fO\nczt59vTZkxy5SBo0YMDQ5dgxc+bONXX6FGpTAFOpVrV6FWtWrVu5nvP69Su5ZMmqVKGDBIkNGxIk\nXFiwgACBATNmlCt3Dm9evXvxAvD7F/A5wYMJC/72zdeTJ0yYkP8g8WbPnlSpZJkzdw5zZs2bNQPw\n/Bn0OdGjR5sTJw4VKkd69CBCtG3bOdmzade2bRtAbt27z/X2/Rt4b3DgECAAcPx4AAMGKlSwZInc\nOenTqVMHcB17du3buXf3/h38OfHjx5MzZuzLlwcIEAgQMGCAAPkA6JMgcQ5/fv379QPwDxCAwIEA\nzhk8iNAgMmQSAgQAABFAAAIUCTAwYYIHjxgxOLx4ESWKFG/ezpk8eQ6AypUsz7l8+XLbokURIggo\nUODChWTJzvn8CTSoUKEAiho9ei6p0qVMzZnbsAGAVKkDqgYIACArgATixJ37CjbsVwBky5o9izat\n2rVs2557Cxf/Ljljxr58eYAAgQABAwYI+AsgMAkS5wobPoz4MIDFjBufeww58mNkyCQECAAgM4AA\nBDoTYGDCBA8eMWJwePEiShQp3rydew37HIDZtGufu40b97ZFiyJEEFCgwIULyZKdO448ufLlywE4\nfw79nPTp1KubM7dhA4Dt2wd4DxAAgHgACcSJO4c+vXr0ANq7fw8/vvz59OvbP4c/v3784sQRAwgL\n1qJFkyaZUaEiQAAACxacgxhR4kSJACxexHhO40aOGqNFKyFAAACSJAUICBAAwEqWLQMEMFCokDlz\n52zaBJBT585zPX2eI0eulgULAQIAIEBAgoRIkcCZM1euXDhq/9Rw4bpyZRE2bOe8fgVLjhwAsmXN\nnkObVu1aceIGDAAQd8ECOHAAMWAQIAAAvggQnAMcWHC5cgAMH0acWPFixo0dPz4XWfLkyOLEEYMF\na9GiSZPMqFARIACABQvOnUadWnVqAK1dvz4XW/bs2NGilRAgAMDu3QIEBAgAQPhw4gECGChUyJy5\nc82bA4AeXfo56tXPkSNXy4KFAAEAECAgQUKkSODMmStXLhw1arhwXbmyCBu2c/Xt3ydHDsB+/v3P\nATwncCBBguLEDRgAYOGCBXDgAGLAIEAAABYRIDincSPHcuUAgAwpciTJkiZPokx5biXLli5fnmPG\nLEGCAHr0nP/LqXMnz50AfgINem4o0aJFaw0YAGApgAEZMjhwoEAAVQEAAAQYMCBIEFfgwJ0LK/Yc\ngLJmz55Lq/ZcuXKoFiwAILdAgRMnFi0SBQiQFy88SJC4cMGAgReuXJkzd24x48XmzAGILHnyucqW\nL2M2Z06BAgAAArhyZW40NWoaNABITYCAN2/nXsN+Xa4cgNq2b+POrXs3796+zwEPLnw48XPJkiVI\nUIAZs3POn0OPDh0A9erWz2HPrh27OXODBAgAAMCAgRRu3IgRIwEChAIFDhzQcOwYOXLn7uPPD2A/\n//7nAJ4TOHDcODUDBgAAEODAAQgQEiQwECAAAIsBAgjQKKD/ABgw4sSdEzlSpDlzAFCmVHmOZUuX\nL8uVc+AAAIAH2bKdO2euXDlKlAQIACBAgBs35c4lVXpOnDgAT6FGlTqValWrV7Ge07qVa1ev55Il\nS5CgADNm59CmVbtWLQC3b+GekzuXrlxz5gYJEAAAgAEDKdy4ESNGAgQIBQocOKDh2DFy5M5FljwZ\nQGXLl89l1nxu3Dg1AwYAABDgwAEIEBIkMBAgAADXAQIIkC2gABgw4sSd071btzlzAIAHF36OeHHj\nx8uVc+AAAIAH2bKdO2euXDlKlAQIACBAgBs35c6FF39OnDgA59GnV7+efXv37+Gfkz+ffn375548\nCRBgADdu/wDPCRxIsCBBAAgTKjzHsKFDh04CSAzw4UOfWLGUKHFw4ECCBCpUNDtHsqRJkwBSqlx5\nrqXLc+XK5SlQAIBNAQIWLChQIACAnwASKFBw4ECBAhNYsTrHtKlTpgCiSp16rqrVq1jNmcOAAQAA\nAa9emTN3jhw5MWIOHAggQMCgQebOyZ07F4Ddu3jz6t3Lt6/fv+cCCx5MuPC5J08CBBjAjdu5x5Aj\nS44MoLLly+cya9682UmAzwE+fOgTK5YSJQ4OHEiQQIWKZudiy549G4Dt27jP6d59rly5PAUKABgu\nQMCCBQUKBADAHEACBQoOHChQYAIrVueya9+eHYD37+DPif8fT768OXMYMAAAIODVK3PmzpEjJ0bM\ngQMBBAgYNMjcOYDnBA4UCMDgQYQJFS5k2NDhw3MRJU6kOFGcuDAGDAAAUCBXrnMhRY4kORLASZQp\nz61k2XLluHEOChQwYGDKlCRWrDx4QECAgAULbNiIds7oUaRIASxl2vTcU6jnxImLxYBBgAAACBBA\ngECBggIUKIwY8UOFiggRDhwQYc3aObhx5cIFUNfu3XN59e7la84cAgQAAAyoVWvcuHPduh06NGEC\ngwsXqFE7V9lyZXPmAGzm3NnzZ9ChRY8mfc70adSpUYsTF8aAAQAACuTKdc72bdy5cQPg3dv3OeDB\nhQMfN87/QYECBgxMmZLEipUHDwgIELBggQ0b0c5t5969OwDw4cWfI1/+nDhxsRgwCBAAAAECCBAo\nUFCAAoURI36oUBEhAsADB0RYs3buIMKEBwEwbOjwHMSIEieaM4cAAQAAA2rVGjfuXLduhw5NmMDg\nwgVq1M6xbMnSnDkAMmfSrGnzJs6cOnee6+nzJ9CexoylSHFAgAACBBj48XPuKdSoUqMCqGr16rms\nWrdmxYZNCgcOQYJQoYLjwoUECQYECDBgQIMGeMiRO2f3Ll67APby7XvuL2DA4Xr1MmNGCyFCqVKF\nCvVr2rRx47INGsSAwYEDiM5x7uzZM4DQokefK236NOpy/+UmTEiQoJA5c+dmmzPny5cdO1xy5Trn\n+zdw3wCGEy9u/Djy5MqXMzdn7hz06NKhmwsWzIGDAAEIKFDAgIGCBw+YMDl27Bz69OrXA2jv/v25\n+PLnlyuHCFEgNGhSpHjwAGCEBQsOHBhwUICAAAEGRIny7ds5iRMpArB4EeM5jRs5litHjpy5ciPL\nhQtH7lzKc+LQoDlwYMKEcOdo1rRpE0BOnTvP9fT5E+i0aQsWsGCx7VzSpOXKsWIVIwYecuTOVbV6\ntSoArVu5dvX6FWxYsWPNmTt3Fm3as+aCBXPgIEAAAgoUMGCg4MEDJkyOHTv3F3BgwQAIFzZ8DnFi\nxeXKIf9CFAgNmhQpHjyIsGDBgQMDOAsQECDAgChRvn07dxp1agCrWbc+9xp27HLlyJEzVw53uXDh\nyJ3zfU4cGjQHDkyYEO5ccuXLlwNw/hz6OenTqVefNm3BAhYstp3z7r1cOVasYsTAQ47cOfXr2asH\n8B5+fPnz6de3fx//Of37+fPfBvDMmQgRGDAAM2uWLVseDBggQCBIkGvnKlq8eBGAxo0cz3n8+NHc\ntm2yZKWxYePChQcPGPz4kSrVLVmyrFjRoCEAAAAKFJz7CTQogKFEi547ijSp0qVKzdmwIUBAlizn\nqlq9ihWA1q1cz3n9CjasLl0fPlCiZO6c2nPicuXq0SP/QgQ/5cqdu4s3710AfPv6/Qs4sODBhAuf\nO4w4ceJtZ85EiMCAAZhZs2zZ8mDAAAECQYJcOwc6tGjRAEqbPn0utWrV5rZtkyUrjQ0bFy48eMDg\nx49UqW7JkmXFigYNAQAAUKDgnPLlzAE4fw79nPTp1Ktbr27Ohg0BArJkOQc+vPjxAMqbP38uvfr1\n7HXp+vCBEiVz5+qfE5crV48eESL4AViu3DmCBQ0SBJBQ4UKGDR0+hBhRojlz5yxexEiOXDRJkl69\nIkfu3MiR5LBgWbCAAIEez56dgxlTJkwANW3ePJdT57lx45ypUQMESAUHDhIkePGCjzlz55w+PTdu\nHAEA/wACBKh2TuvWrQC8fgV7TuxYsmXNlgWXQG0CbdrOvYUbVy4AunXtmjN3Tu9evnrHtWkzYYIV\nK8isWUOFasODBwoUHDgw4datc5UtX64MQPNmzp09fwYdWvToc6VNmx4nTly2bNzOvYYdO/aoUQUK\nLJAl69xu3r13AwAeXPg54sTLlYsVK02GDAwYLIAAYcqUb9/OXcee/XoXAAAECDB3Tvz48QDMn0d/\nTv169u3dt+dVoMCNG+fs38ef3z4A/v39AzRn7hzBggbLlQOVIAEBAgsWKFiwQADFAAEIEAgQgAAP\nHuc+ggz5EQDJkiZPokypciXLludewoQ5Tpy4bNm4nf/LqXPnzlGjChRYIEvWuaJGjxYFoHQp03NO\nnZYrFytWmgwZGDBYAAHClCnfvp0LK3Zs2C4AAAgQYO4c27ZtAcCNK/cc3bp27+K9y6tAgRs3zgEO\nLHgwYACGDyM2Z+4c48aOy5UDlSABAQILFihYsEAA5wABCBAIEIAADx7nTqNOfRoA69auX8OOLXs2\n7drmzJ3LbW63uWzHjunSVe4c8eLGjVOjNmCAAWLEzkGPLh06gOrWr5szd257t267dqEoUCBAgAUm\nTPjydW49+/btRQUIcOAAuXP2798HoH8//3P+AZ47Z87cOYMHESY0aM6cjQMHbt06N5FiRYsTAWTU\nuJH/HLlzH82ZOzdSmjQnTiQMGAAAQACXAGDCFDBTQIAAACpUIEfuXE+fPwEEFTqUaFGjR5EmVWrO\n3Dmn5qCay3bsmC5d5c5l1bp1KzVqAwYYIEbsXFmzZ8sCULuWrTlz5+B267ZrF4oCBQIEWGDChC9f\n5wAHFixYVIAABw6QO7eYMWMAjyFHPjd5sjlz5zBn1rwZszlzNg4cuHXrXGnTp1GXBrCadWty5M7F\nNmfuXG1p0pw4kTBgAAAAAYADEC5cQHEBAQIAqFCBHLlzz6FHBzCdenXr17Fn176de7ly58CHPxeu\nWbNatcydU7+ePXtBggQISKBN2zn79/HbB7Cff39z/wDNnRtozpwyZRoCBAAAQIALF+HCnZtIsWLF\nOAAAGDBg7pzHjx8BiBxJ8pzJkyhTqjxZrtwEAQKePTtHs6bNmzQB6NzJc9w4c+fOmTPnzZuqBw8C\nBAAQoKlTAFADBBBANUAAAFgFCHDm7JzXr2ABiB1LtqzZs2jTql1brty5t3DPhWvWrFYtc+fy6t27\nV5AgAQISaNN2rrDhw4UBKF7M2Jy5c5DNmVOmTEOAAAAACHDhIly4c6BDixYdBwAAAwbMnVvNmjWA\n17Bjn5tNu7bt27TLlZsgQMCzZ+eCCx9OPDiA48iTjxtn7tw5c+a8eVP14EGAAAACaN8OoHuAAALC\nB/8IAKC8AAHOnJ1bz749gPfw48ufT7++/fv4y5U7x78/f4B9+jBgEOzcQYQJD0aLFiGCAAFLzJk7\nV9HixYoANG7kaM7cOZAgwYGbBMCkSRYsmDE719Lly5bevAkAAECDhnM5de4E0NPnz3NBhQ4lWlTo\nsWMClIIDd87pU6hRnQKgWtUqOHDjvHnz5WvKlA4BxAYYcOFCkCA6dCxo0ODAWwFxBQCgK0DAqFHn\n9O7lC8DvX8CBBQ8mXNjw4XOJFS/WpUuAgAjmzJ2jXLlyuAULBAhIkcLcOdChRYsGUNr06XOpVasm\n9+ABANgHDkiR8urVMmjQcOHqFi0aLlwDBgAg/un/0znkyZUDYN7c+Tno0MuVM2fu3HXs2bVr05ag\nQAFz5s6NJ1/e/HgA6dWvDxdO3HtUqAoVGoIDR6ZM387t52/uGsBrt24Z4cBBgYIAChEgECbsHMSI\nEgFQrGjxIsaMGjdy7HjuI8iQunQJEBDBnLlzKleuDLdggQABKVKYO2fzJk6cAHby7HnuJ1Cg5B48\nAGD0wAEpUl69WgYNGi5c3aJFw4VrwAAAWj99Ouf1K1gAYseSPWfWbLly5syda+v2LVxt2hIUKGDO\n3Lm8evfyzQvgL+DA4cKJK4wKVaFCQ3DgyJTp27nIks1du3brlhEOHBQoCOAZAQJhws6RLm0aAOrU\n/6pXs27t+jXs2Odm064NDlyAAANKlAgX7hxw4Jw4KRAgoEEDb97OMW/u/DmA6NKnn6tu/boxYw0a\nLECAYAD4AQIAkAcgwIABAOrV79lz7j38+O8B0K9v/xx+/Nq0Zct2DuA5gQMJEhw3zgEIEObMnXP4\nEGJEhwAoVrS4bVs4cOCOHVP2cdu2cyNJlhw5bhwuX76ePDlwYIAMGdGinbN5EycAnTt59vT5E2hQ\noUPPFTV6FBy4AAEGlCgRLtw5qVI5cVIgQECDBt68nfP6FWxYAGPJlj13Fm1aY8YaNFiAAMEAuQME\nALALQIABAwD48t2z51xgwYMDAzB8GPE5xYq1af/Llu1cZMmTKY8b5wAECHPmznX2/Bl0ZwCjSZfe\nti0cOHDHjilzvW3bOdmzacseNw6XL19Pnhw4MECGjGjRzhU3fhxAcuXLmTd3/hx6dOnnqFe3Th0O\nHADbtxcoIAA8AAAEcOAoV+5cevXr2acH8B5+/HPz6defT44csEGDJElKATDFgAAEAzxo0ECCBCNG\npp17CDFiRAAUK1o8hxFjuXLUqJ37CDKkyI+ZLFgwZ+6cypUsW6oEADOmzG7dypkzR45cuXLnevr8\nCRRot26cOGlAgQIcuHNMmzoFADWq1KlUq1q9ijXrua1cu3b9ZcFCgLEBAAQIkCCBrHNs27p9Cxf/\ngNy5dM/ZvYs3r15zfPme+ws4sODB5wAYPoz4nOLF57x5M3cusuTJlM9JW7LknObNnDtzBgA6tGhz\n5s6ZPo06terVp4udOnUutuzZsQHYvo07t+7dvHv7/n0uuPDhw39ZsBAgeQAAAQIkSCDrnPTp1Ktb\nB4A9u/Zz3Lt7/w7enHjx58qbP48+/TkA7Nu7Pwc//jlv3sydu48/v/5z0pYsAXhO4ECCBQkCQJhQ\noTlz5xw+hBhR4sSHxU6dOpdR48aMADx+BBlS5EiSJU2ePJdS5UqWLV2+hKkSwEyaNc/dxJlT506e\nPX3iBBBU6NBzRY0eRZoUqbly5c49hRpValQA/1WtXj2XVetWrl29fgWrFcBYsmXNnkWbVu1atufc\nvoUbV+5cunXfAsCbV+85vn39/gUcWPDgvgAMH0Z8TvFixo0dNzZXrtw5ypUtX7YMQPNmzuc8fwYd\nWvRo0qU/A0CdWvVq1q1dv4Yd+9xs2rVt38adWzdtAL19/z4XXPhw4sWNH0cuHMBy5s3PPYceXfp0\n6tWtQweQXfv2c929fwcfXvx48t4BnEefXv169u3dv4d/Tv58+vXt38effz4A/v39AzwncCDBggYP\nIkw4EADDhg7PQYwocSLFihYvRgSgcSPHcx4/ggwpciTJkh8BoEypciXLli5fwox5bibNmjZv4v/M\nqZMmgJ4+f54LKnQo0aJGjyIVCmAp06bnnkKNKnUq1apWoQLIqnXrua5ev4INK3YsWa8AzqJNq3Yt\n27Zu38I1Z+4c3bp27+LNq3fvOQB+/wI+J3gw4cKGDyNOPBgA48aOz0GOLHmyOXPnLmPOrHkzZ8wA\nPoMOfW406dKmT6NOrZo0gNauX8OOLXs27dq2zZk7p3s3796+fwMPfg4A8eLGzyFPrnw58+bOnycH\nIH069XPWr2PPbs7cue7ev4MPL947gPLmz59Lr349+/bu38NXD2A+/fr27+PPr38///7+AQIQOJBg\nQYMHESZUuJBhQ4cPIUaUOJFiRYsXMWbUuJH/Y0ePH0GGFDmSZEmTJ1GmVLmSZUuXL2HGlDmTZk2b\nN3Hm1LmTZ0+fP4EGFTqUaFGjRxeOG0fOXFOn5s5FlTqV6jlzV6+e07qVa1etAMCGFXuObFmzZ9Gm\nVbu2LAC3b+GaM3eObt1z5s7lzWuOb1+/fM8FFjyYcGEAhxEnPreYcWPHjyFHlnyuXDkAlzFn1ryZ\nc2fPn0GPG0fOXGnT5s6lVr2a9Tlzr1+fkz2bdm3ZAHDn1n2Od2/fv4EHFz68NwDjx5GbM3eOefNz\n5s5Fj26OenXr1M9l176de3cA38GHPzeefHnz59GnV3+uXDkA7+HHlz+ffn379/GbM3eOf3///wDP\nCRxIUKA5c+PKlTvHsKHDhw4BSJxI8ZzFixgzatzIseNFACBDijxHsqTJkyjPmTN3rqXLlzBjugRA\ns6bNczhz6tzJs6fPnzkBCB1KtKjRo0iTKl16rqnTp1CjOjVnrps0aeeyat3KdSuAr2DDnhtLtqzZ\ns2jTqiULoK3bt+fiyp1Lt67du3jlAtjLt++5v4ADCx5MuLBhwAASK17MuLHjx5AjSz5HubLly5gr\nmzPXTZq0c6BDix4tGoDp06jPqV7NurXr17BjrwZAu7btc7hz697Nu7fv37kBCB9O/Jzx48iTK1/O\nvPlxANCjS59Ovbr169izn9vOvbv379y7df9zkyULOXLn0qtfzz49gPfw45+bT7++/fv48+unD6C/\nf4AABAI4V9DgQYQJFS5kaBDAQ4gRz02kWNHixXPmzG3bVu7cR5AhRY4EUNLkSZQpVa5k2dLlOZgx\nZc6kGbNbNzdZspAjd87nT6BBfQIgWtToOaRJlS5l2tTp06QApE6les7qVaxZtW7l2vUqALBhxZ4j\nW9bsWbTnzJnbtq3cObhx5c6lC8DuXbx59e7l29fv33OBBQ8mXFgwNWoMChQIFercY8iRJT8GUNny\n5XOZNW/m3NkzZ3PnRI8mTRrAadSpz61m3dr1a9iutyFDZs7cOdy5dQPg3dv3OeDBhQ8nPi7/T55L\nl8CdY36uXDlckCBhwtSsXLlz2bWfA9Dd+3fw4cWPJ1/e/Dn06dWvZ5+eGjUGBQqECnXO/n38+e0D\n4N/fP8BzAgcSLGjwYEFz5xYybNgQAMSIEs9RrGjxIsaMF7chQ2bO3LmQIkcCKGny5LmUKleybDku\nT55Ll8Cdq3muXDlckCBhwtSsXLlzQoeeA2D0KNKkSpcyber06bmoUqdSrSpVmjQCBgxkyPDrF7lz\nYseSJQvgLNq059aybev2Ldxz1aqpUoXDmLFzevfy1QvgL+DA5wYTLmz4MGLC4MBJCBHi2LFzkidT\nBmD5MmZz5s5x7uz5M+dp00AkSPDixR5g/8DgwLFgQcCAAQcOeFi27Bzu3OcA8O7t+zfw4MKHEy9+\n7jjy5MqXIy9XzgKA6AACBDggREi3bubOce/eHQD48OLPkS9v/jz686JmzLhwYcAAAAgQjBt37j7+\n/AD28+9/DuA5gQMJFjR4UKAcOQAYNmgA7FxEiRIBVLR40Zy5cxs5dvR46hQAkSIXLDBSocKBAwBY\nshwwQEKyZOdo1jwHAGdOnTt59vT5E2jQc0OJFjV6lGi5chYANAUQIMABIUK6dTN3DmvWrAC4dvV6\nDmxYsWPJjhU1Y8aFCwMGAECAYNy4c3Pp1gVwF2/ec3v59vX7FzBfOXIAFG7QANg5xYsXA/9w/Biy\nOXPnKFe2fPnUKQCbNy9YYKRChQMHAJQuPWCAhGTJzrV2fQ5AbNmzade2fRt3bt3nePf2/Rt4b2jQ\nDgAwDiBAgAUOHBAjFu1cdOnSAVS3ft2cuXPbuXf33t1cq1YGDAAwLwC9AADrd+069x5+fADz6dc/\ndx9/fv35zZmjBnDatHLlzpkz16sXAQIAGgoQsMCJk2bNzlm0CCCjxo3ixJ37CDLkR3OdOgE4eVKA\nADJkiOXJo0MHBw4gRoxIkmSGL1/mzJ37+ROA0KFEixo9ijSp0qXnmjp9CjWqU2jQDgC4CiBAgAUO\nHBAjFu2c2LFjAZg9i9acuXNs27p969b/XKtWBgwAuCsgrwAAfHftOgc4sGAAhAsbPoc4seLFis2Z\nozZtWrly58yZ69WLAAEAnAUIWODESbNm50qXBoA6tWpx4s65fg3btblOnQDYti1AABkyxPLk0aGD\nAwcQI0YkSTLDly9z5s45dw4guvTp1Ktbv449u/Zz3Lt7/w6++6VLBgIEUKCgUiVy59q7f99+3DgA\n9OvbP4c/v/79+MmRA0iIUAUCBAAACKBAgQgRBQoAgLht2zmKFS0CwJhR4zmOHT1+9KhK1R5duoIF\no7ZrFwoUAQIAgAkzwIEDLVpw43bOnDkAPX3+NGfu3FCiRcGBCxMgAACmAAzw4nVOqtRy/+XOnRvn\nzZsvX42ECTNn7tzYsQDMnkWbVu1atm3dvj0XV+5cunXLOXGiQAEBCRJkyToXWPBgceK+ffMWLRoA\nxo0dmzN3TvJkypLNGTNGgUKAAAA8BwjgIEwYLFgaNACQIAE5cudcv4YNQPZs2ubMncOdW3duc+aI\nEFmwgAMoUK5cFQEBggABAM0HDCBAQAABAgcOXLliixs3AN29fydH7tz4cuXLeXPjhgEDAQDcAyhQ\nANg5+vXt0xcnTpcuQrBgATRn7hxBc+YAIEyocCHDhg4fQox4biLFihYvlnPiRIECAhIkyJJ1biTJ\nkuLEffvmLVo0AC5fwjRn7hzNmjZpmv8zZowChQABAAANEMBBmDBYsDRoACBBAnLkzkGNKhUA1apW\nzZk7p3Ur163mzBEhsmABB1CgXLkqAgIEAQIA3g4YQICAAAIEDhy4csUWN24A/gIOTI7cucLlDpfz\n5sYNAwYCAEAGUKAAsHOWL2O2LE6cLl2EYMEyZ+4caXPmAKBOrXo169auX8OOfW427dq2b4cIEECA\nAA27dp0LLnx4cG3amDHzFi4cgObOn5crd2469erTv+XIMWBAgAAGUKA4dapZt26YMF24IKBDh3Pu\n38N3D2A+/frn7uPPrx8WLAUKADZoQMqaNWXKLABQuPADJEjQoIGjRu3YMWvWxI0bB4D/Y0eP5Mid\nE2nOnDBhRwakHCBgwAAUKHTpOjeTZs2Z5SRJIkJEFjly54AGPQeAaFGjR5EmVbqUadNzT6FGlTo1\nRIAAAgRo2LXrXFevX7tq08aMmbdw4QCkVbu2XLlzb+HGffstR44BAwIEMIACxalTzbp1w4TpwgUB\nHTqcU7yYsWIAjyFHPjeZcmXLsGApUNCgASlr1pQpswCAdOkPkCBBgwaOGrVjx6xZEzduHADbt3GT\nI3eOtzlzwoQdGTB8gIABA1Cg0KXrXHPnz5uXkySJCBFZ5Mid0779HADv38GHFz+efHnz58+lV7+e\n/fobNwAIEJAiRTFy5M7l178//7Fj/wDBgStHjhyAgwgTkiN3rmFDc+bOmTMnTRqPAgUECJgwQZE3\nb+bMnTNnrlq1ECEKuHBxrqXLly0ByJxJ05y5czhz6sS5q0KFAAEsWDjWrVuhQgEAKAXw4QO5c1Cj\nSoVqzhyAq1izkiN3rqs5c8GCKTFggACBBVGiwIIlTty5t3DjevN2Q4GCLl3MndvLly+Av4ADCx5M\nuLDhw4jPKV7MuDHjGzcACBCQIkUxcuTOad7MWfOxY+DAlSNHDoDp06jJkTvHmrU5c+fMmZMmjUeB\nAgIETJigyJs3c+bOmTNXrVqIEAVcuDjHvLlz5gCiS59uzty569izX99VoUKAABYsHP/r1q1QoQAA\n0gP48IHcuffw4783Zw6A/fv4yZE7x9+cOYDBgikxYIAAgQVRosCCJU7cOYgRJXrzdkOBgi5dzJ3j\n2LEjAJAhRY4kWdLkSZQpz61k2dLlSjx4AgQoQIvWOZzlyp3j2dPnuHHlyp0jShTAUaRJzZk719Rp\nU3LkChViYMBAlCjHjpU719XruXDhVqw4YMrUObRp1aIF0Nbt23Nx5c6N260bDAECAgSAAiUbNWo4\ncAQgPGLEOcSJFS9GDMDxY8jmzJ2jXPkcuWnTCBHac+gQNmzdup0jXdqcOViwDBgAMGBAtWrnZM+m\nDcD2bdy5de/m3dv373PBhQ8nHir/FADkAN6cY37OXLNmhw7x4ePr2zds2KqVK3fO+/dzAMSPJ3/O\n/Pnz5pAhS5JkBxYsyZKNG1fu3H3858iR06DhAMBv384RLGiQIICECheea+jwYblyffowIEAAAYIt\nW2gRIVKggIAiRc6RLGnypEkAKleyPOfyJUyXzZpNcuKkSxc8eLT58jVnjoYMGQAQJUqAgDhx55Yy\nbQrgKdSoUqdSrWr1KtZzWrdy7RoqFICwAN6cK3vOXLNmhw7x4ePr2zds2KqVK3fuLt5zAPby7Xvu\nL2DA5pAhS5JkBxYsyZKNG1fuHOTI58iR06DhwLdv5zZz7rwZAOjQos+RLm26XLk+/30YECCAAMGW\nLbSIEClQQECRIud28+7tuzeA4MKHnytu/HjxZs0mOXHSpQsePNp8+ZozR0OGDAC2bydAQJy4c+LH\nkwdg/jz69OrXs2/v/v25+PLnz68E4D4AChTMnevfH2CjRgoUBAggAAAAAwZonXP48CEAiRMpnrN4\n8aI5cuSmTRNXrtw5kefKnTN58ty0aQ0aICBH7lxMmTNjArB5E+c5nTt5kiNHh06IAwcsWLhxA8KA\nAQIEaPDm7VxUqVOpTgVwFWvWc1u5dt0aLlyGAAEIEKBAwQICBALYAnD7FoABA926nbN7Fy8AvXv5\n9vX7F3BgwYPPFTZ8+HAlAIsBUP+gYO5c5MiNGilQECCAAAAADBigdQ506NAASJc2fQ516tTmyJGb\nNk1cuXLnaJ8rdw537nPTpjVogIAcuXPDiRcfDgB5cuXnmDd3To4cHTohDhywYOHGDQgDBggQoMGb\nt3PjyZc3Xx5AevXrz7V3/759uHAZAgQgQIACBQsIEAjwDxCAwIEADBjo1u2cwoUMATh8CDGixIkU\nK1q8eC6jxo0ZTZkKAABAgACgQJ07idKatSJFBAgAALNAAW/natq0CSCnzp3nevr0aY4cOXPmzhk9\nijQpKFADBjwoV+6c1KlUpQK4ijXrua1cuZo7dsyLlxcgQECAYMBAgLUDBkDx5u3/nNy5dOvSBYA3\nr95zfPv65atIEYDBgwMYBoA4sWIAARoTInQusuTJACpbvow5s+bNnDt7Pgc6tOhx4xgwAIAaCpRz\nrFu75sZNggQAtG3YOIc7t24AvHv7Pgc8uPDhxImbM4cBQ4ECn845fw4dOoDp1Kufu44d+zhv3oAB\n22XHDgUKBQoIMGAgRQpC3bqdew8/vvz4AOrbv38uv/794MAVAFgAwEABAhgwMCBAQIAAAwwYePCA\nBYsKBgyAAHHt3EaOHAF8BBlS5EiSJU2eRHlO5UqW48YxYABAJhQo52zexMmNmwQJAHzasHFO6FCi\nAIweRXpO6VKmTZ06NWcOA4YC/wU+ncOaVatWAF29fj0XVqzYcd68AQO2y44dChQKFBBgwECKFIS6\ndTuXV+9evnsB/AUc+NxgwoXBgStQAMBiAQIYMDAgQECAAAMMGHjwgAWLCgYMgABx7dxo0qQBnEad\nWvVq1q1dv4Ztztw52rVpa9IUIAAAAwZcuToXXPhwbdoaNAAwYECvXuecP4cOQPp06uesX8eeXbt2\nK1YGDJgwgdw58uXNmweQXv16c+bOvX9vzlw5cuS8eRPXrFmQIB8+AGyRJAkbNl1ChSJH7hzDhg4f\nMgQgcSJFceLOYcx4bhoCBAA+KlDgwYMECSccOPjwAUOVKly4xIlzgwWLBAkszP+adW4nz3MAfgIN\nKnQo0aJGjyI1Z+4c06ZMNWkKEACAAQOuXJ3LqnWrNm0NGgAYMKBXr3Nmz6IFoHYt23Nu38KNK1eu\nFSsDBkyYQO4c375+/QIILHiwOXPnDh82Z64cOXLevIlr1ixIkA8fWiRJwoZNl1ChyJE7J3o06dKi\nAaBOrVqcuHOuX5+bhgABgNoKFHjwIEHCCQcOPnzAUKUKFy5x4txgwSJBAguzZp2LLv0cgOrWr2PP\nrn079+7ey5U7J368eFy4AgQAYMCAKVPmzJ2LH1/crl0OHAQIAMCAAXLkAJ4TOJAgAIMHEZ5TuJBh\nQ4cNYwmQKODZs3MXMWbUCID/Y0eP50CGFDlSnDhr1ooV05YsGS1aLEKE+PbtXE2bN3HWBLCTZ09v\n3siZM+fNmxEjAQAACBCAwZAhMWJMmHBgwAADBgocOIABAw8eOhgwKFDggA4dvnyZM3eOLQC3b+HG\nlTuXbl27d8uVO7eX715cuAIEAGDAgClT5sydU6xY3K5dDhwECADAgAFy5M5l1rwZQGfPn8+FFj2a\ndGnSsQSkFvDs2TnXr2HHBjCbdu1zt3Hn1i1OnDVrxYppS5aMFi0WIUJ8+3aOeXPnz5kDkD6dujdv\n5MyZ8+bNiJEAAAAECMBgyJAYMSZMODBggAEDBQ4cwICBBw8dDBgUKHBAhw5f/wB9mTN3riCAgwgT\nKlzIsKHDhxDLlTtHsSJFMmQAaHTg4McPQIAOJUqkQEEAAChTFogU6ZzLlzBdAphJs+a5mzhz3hQn\nDty5n0CB9upVQIAAWrTOKV3KtKlSAFCjSj1HtarVq1ir1qrFAAECbtzOiR1Ltqw5cwDSql07bpw4\nadJChAgQAIBdAgRSYMJEhcqRIwskSLhw4UGBAh8+gAEzZsqUHTssZMiQJg05cucyA9jMubPnz6BD\nix5N+pzp06iPHTNgIMCA1wMECABAuzbtAAGwYBF3rrfv378BCB9O/Jzx48iNixOXixq1cePKlTNX\nq9aCBQyCBTvHvbv3794BiP8fT/6c+fPo06s/782bhQQJuHE7R7++/fvmzAHYz7+/OYDmyIEDBwKE\nAAEABAgYMWLVsmXZsoULV86cuXPnynHjhg3buHHgvn0LFqyLDBnHjp1jyRLAS5gxZc6kWdPmTZzn\ndO7keeyYAQMBBgwdIEAAAKRJkQYIgAWLuHNRpU6dCsDqVazntG7lqlWcuFzUqI0bV66cuVq1Fixg\nECzYObhx5c6VC8DuXbzn9O7l29fvXm/eLCRIwI3bOcSJFS82Zw7AY8iRzZkjBw4cCBACBAAQIGDE\niFXLlmXLFi5cOXPmzp0rx40bNmzjxoH79i1YsC4yZBw7ds63bwDBhQ8nXtz/+HHkyZWfY97c+bVr\nKFB8UKAAwHXs2R88cOTInLlz4cWPJw/A/Hn059SvZ6/enLliWLCsWPHmDSEGDAYM0HLOP8BzAgcS\nLDgQAMKECs8xbOjwIcSG48bpgAFj2TJz5s5x7OixY7lyAEaSLFmu3Dlz5ggR0qABwYEDXbpwI0fu\nHM6cOneeK2fOnDdvsIABM2fuHFKkAJYyber0KdSoUqdSPWf1KtZr11Cg+KBAAYCwYsc+eODIkTlz\n59aybesWANy4cs/RrWuXrjlzxbBgWbHizRtCDBgMGKDlHOLEihczBuD4MeRzkidTrmx58rhxOmDA\nWLbMnLlzokeTHl2uHIDU/6pXlyt3zpw5QoQ0aEBw4ECXLtzIkTvn+zfw4OfKmTPnzRssYMDMmTvn\n3DmA6NKnU69u/Tr27NrPce/unfu3b6fkyGHEyI4dP3LkYMNW7hz8+PLn0wdg/z7+c/r38+dvDuCa\nNT9+9OhRAQOGRo3ONXT4EGLEcwAoVrR4DmNGjRs5bhz36JEsWebMnTN5EmVKACtZtjz38iU3bjhw\nJNCgoVUrc+d49vT50yc5crp0jTNn7lxSpecANHX6FGpUqVOpVrV6DmtWrVu5dvX6NSsAsWPJnjN7\nFm1as9CgtWrl6No1cuTO1bV7F2/ecwD49vV7DnBgwYMJFz5nztw5xYsZN/9WDAByZMnnKFc+d+2a\nsmrVypU79xl0aNGizZkrV+5catWrAbR2/Rp2bNmzade2fQ53bt27eff2/Ts3AOHDiZ8zfhx5cuPQ\noLVq5ejaNXLkzlW3fh179nMAuHf3fg58ePHjyZc/Z87cOfXr2bdXDwB+fPnn6Nc/d+2asmrVypU7\nB/CcwIEECxI0Z65cuXMMGzoEADGixIkUK1q8iDHjuY0cO3r8CDKkSI4ASpo8eS6lypUsW7p8CVMl\ngJk0a567iTOnzp08e/rECSCo0KHniho9ijSp0qVMjQJ4CjWq1KlUq1q9ivWc1q1cu3r9CjbsVgBk\ny5o9hzat2rVs27p9mxb/gNy5dM/ZvYs3r969fPveBQA4sOBzhAsbPow4seLFhQE4fgw5suTJlCtb\nvnwus+bNnDt7/gxaM4DRpEufO406terVrFu7Rg0gtuzZ52rbvo07t+7dvG0D+A08+LnhxIsbP448\nuXLiAJo7fw49uvTp1KtbP4c9u/bt3Lt7/54dgPjx5M+ZP48+vfr17NufBwA/vvxz9Ovbv48/v/79\n9QH4BwhA4EAA5wweRJhQ4UKGDQ8CgBhR4kSKFS1exJjx3EaOHT1+BBlSJEcAJU2ePJdS5UqWLV2+\nhKkSwEyaNc/dxJlT506ePX3iBBBU6NBzRY0eRZpU6VKmRgE8hRpV6lSq/1WtXsV6TutWrl29fgUb\ndisAsmXNnkObVu1atm3dvk0LQO5cuufs3sWbV+9evn3vAgAcWPA5woUNH0acWPHiwgAcP4YcWfJk\nypUtXz6XWfNmzp09fwatGcBo0qXPnUadWvVq1q1dowYQW/bsc7Vt38adW/du3rYB/AYe/Nxw4sWN\nH0eeXDlxAM2dP4ceXfp06tWtX8eeXft27t29fwcfXvx48uXNn0efXv169u3dv4cfX/58+vXt38ef\nX/9+/v39AwQgcCDBggYPIkyocCHDhg4fQowocSLFihYvYsyocSPHjh4/ggwpciTJkiZPokypciXL\nli5fwowpc6ZBc+bO4f80p9OcuHHjypU7J3Qo0aHkyHHjJk5cuXNOn0KFCmAq1armzJ3LqnUrV3Ne\nzZ0LK3Zs2HLhwo0bd24t27YA3sKNe24u3bp27+LNq5cugL5+/5IjV+7cuXLlxo3Ddu3atm3kHpuL\nbO4c5cqUyZEzp/ncOXPmzoEOLRoA6dKmT6NOrXo169bnXsOGTc6cuXO2b+PObW63uXO+fwMP7hsA\n8eLGzyFPrnw58+bLy40bd2469erTAWDPrv0c9+7ev4MPL358dwDmz6M3Z+4ce/blynHbtk2cOHPn\n7uPPr38/f/0AAAIQOJBgQYMHESZUqPBcQ4cPIUaMWK7cOYsXMWbECID/Y0eP50CGFDmSZMmR46RJ\nO7eSZcuVAGDGlHmOZk2bN3Hm1LmzJgCfP4GeEzp0aDly5MyZO7eUaVOm5MiZM3eOalWrV6kC0LqV\na1evX8GGFTv2XFmzZ9GmTVuu3Dm3b+HGhQuAbl275/Dm1buXb9+946RJOzeYcOHBABAnVnyOcWPH\njyFHljy5MQDLlzGf07x5czly5MyZOzeadGnS5MiZM3eOdWvXr1kDkD2bdm3bt3Hn1r37XG/fv4EH\n9y1O3Ldx484lV76c+XIAz6FHPzedenXr17FTJ0duU58+58CHFw8eQHnz58+lV7+efXv37+GrBzCf\nfv1z9/GfM2fOW7Zs/wC7dRtHsFw5c+bOKVQoDhmycOHOSZxIsaJEABgzatzIsaPHjyBDnhtJsqTJ\nkyTFifs2bty5lzBjyowJoKbNm+dy6tzJs6dPneTIberT55zRo0iNAljKtOm5p1CjSp1KtapVqACy\nat16rqvXc+bMecuWrVu3cWjLlTNn7pxbt+KQIQsX7pzdu3jz2gXAt6/fv4ADCx5MuPC5w4gTK16M\nDQ0aI0ZijRt3rrLly5gvA9jMufO5z6BDix5NGnSqVB/atDnHurVr1gBiy559rrbt27fJndvNm/e4\nccSSJStX7pzx48iTGwfAvLnzc9CjRxdXrRouXJtAgfLk6dSpacuWKf9S9CJGjHDhzqlfz769egDw\n48ufT7++/fv485/bz7+/f4DnBA7EhgaNESOxxo0719DhQ4gPAUykWPHcRYwZNW7kiDFVqg9t2pwj\nWdIkSQApVa4819Lly5fkzs2kSXPcOGLJkpUrd87nT6BBfQIgWtToOaRJk4qrVg0Xrk2gQHnydOrU\ntGXLFCl6ESNGuHDnxI4lW1YsALRp1a5l29btW7hxz82lWxccOFeupn36BAUKAgQBAAAQICDEtm3n\nFC9m3JgxAMiRJZ+jXNnyZcyZz1WrhgDBAGzYzo0mXXo0ANSpVZ9j3dq1OXPPnskKE8aJExw42pAg\nsWABgSNHjh07V9z/+HHkxQEsZ9783HPo0Mtp0xYoUJYRIzBg8OCBhQULBw4AaNAAHLhz6dWvZ58e\nwHv48eXPp1/f/n385/Tv3/+tD8A+DhwEAGDwIIAAAQwY4LBpEzdu5yZSrGhxIoCMGjee6+jxI8iQ\nIc2Z06ABAAAF5cqda+nyZUsAMmfSPGfzJk5z5rZt67FgwYABBgwkYMAAAQIAAQJs2DBu3LmoUqdS\nBWD1KtZzWrduNRcunDBhSlq0SJHCipVFN24QIABgwIBy5c7RrWv3Ll0Aevfy7ev3L+DAggefK2zY\n8Lc+fRw4CADgMWQAAQIYMMBh0yZu3M5x7uz5M2cAokeTPmf6NOrU/6pVmzOnQQMAAArKlTtn+zZu\n2wB28+597jfw4ObMbdvWY8GCAQMMGEjAgAECBAACBNiwYdy4c9q3c+8O4Dv48OfGkydvLlw4YcKU\ntGiRIoUVK4tu3CBAAMCAAeXKnevvH+A5gQMJngNwEGFChQsZNnT4ECI5cucoUixXTlumTBMmCAgQ\nAACAAAEIxIhhwkQEBAhQoTr3EmZMc+bO1awJAGdOned49vT505w5cODOFTValBAhAQIAAFBhztw5\nqVOpSgVwFWtWc+bOdfX6tWu5bdu4cevWbRw5csSISQAAQIECWLDO1bV7Fy8AvXv5lit3DnBgwYDL\nFT53+HC5ci9eAP8IEOBcZMmTKU8GcBlzZs2bOXf2/Bk0OXLnSJMuV05bpkwTJggIEAAAgAABCMSI\nYcJEBAQIUKE69xt4cHPmzhUvDgB5cuXnmDd3/tycOXDgzlW3Xp0QIQECAABQYc7cOfHjyYsHcB59\nenPmzrV3/759uW3buHHr1m0cOXLEiEkAABCAAgWwYJ07iDChQgAMGzosV+6cxIkUJZa7eC5jxnLl\nXrwAECDAuZEkS5osCSClypUsW7p8CTOmTHPmztm8iXPcuGmIEPnyFS3auGvXCBGiIELEt2/nmjp9\nKk7cuanmzAG4ijXrua1cu3o1Z65bt3Nky54LFyECAAAFCtQ6Bzf/rly5AOravXsur969fPvyLSVA\nwIABwYKdO4w4sWIAjBs7Pgc5suTJlCPv2gWAAYNznDt7/uwZgOjRpEubPo06terV5sydew07tmzZ\n0KDt2NEgU6ZzvHv75k2OXLhw5MaNA4A8ufJzzJs7d24uXLhs2cyZO4cdOyAA3AE8eFDsnPjx5MkD\nOI8+/bn17Nu7f+9+14EDAgSMGnUuv/79/AH4BwhA4EAA5wweRJhQ4UEjRgAgQXJO4kSKFSkCwJhR\n40aOHT1+BBnSnLlzJU2eRIkSGrQdOxpkynRO5kyaMsmRCxeO3LhxAHz+BHpO6FCiRM2FC5ctmzlz\n55w6BQRAKoAH/w+KncOaVatWAF29fj0XVuxYsmXJ7jpwQICAUaPOvYUbVy4AunXtnsObV+9evnmN\nGAGABMk5woUNHzYMQPFixo0dP4YcWfLkc5UtX8acWVyOHAUKPNi27dxo0qVLlytnrlw5AK1dvz4X\nW/Zs2uXKmTN3TvfucxIAABAgoFMnceeMH0eOHMBy5s3PPYceXfp06ZcGDFiw4Nmzc929fwcPQPx4\n8ufMn0efXv15DhwAMGJ0Tv58+vXpA8CfX/9+/v39AwQgcCDBggYPCjyncCHDhg7F5chRoMCDbdvO\nYcyoUWO5cubKlQMgciTJcyZPokxZrpw5c+dewjwnAQAAAQI6df8Sd24nz549AQANKvQc0aJGjyI9\nemnAgAULnj07J3Uq1aoArmLNem4r165ev3LlwAEAI0bnzqJNqzYtgLZu38KNK3cu3bp2z+HNq3ev\nXm3aJAgQAACAg2vXziFOrHgxYnLkAECOLPkc5cqVzZUr9+3bs2HDqFELF+6cOXNevAAIECBJknHj\nzsGOLXs2gNq2b5/LrXs37966rVlDceAADx7Nmp1Lrnw5cwDOn0M/J3069erWz40bFyAAAC1azoEP\nL368eADmz6NPr349+/bu35+LL38+/fnatEkQIAAAAAfXAF47N5BgQYMDyZEDsJBhw3MPIUI0V67c\nt2/Phg2jRi3/XLhz5sx58QIgQIAkScaNO7eSZUuXAGDGlHmOZk2bN3HWtGYNxYEDPHg0a3aOaFGj\nRwEkVbr0XFOnT6FGPTduXIAAALRoObeVa1evXQGEFTuWbFmzZ9GmVXuObVu3b9lOm7ZgAYAAAQwY\noLFr1zm/fwEHBgyAcGHD5xAnPmfOXDZZso4cuQEECBs2tmwNEyOGAAEBLlx063aOdGnTp0kDUL2a\n9TnXr2HHln3u2DEQIBR48IAI0bJl2c4FFz58OADjx5GfU76ceXPn586cGTBAwJQp57Bn175dOwDv\n38GHFz+efHnz58+lV7+e/adPAwYAkK9AAQoUGT58ePPGmbNz/wDPCRxIkCCAgwgTnlvI8Bw5cpkc\nOAgQwECDBhMmUKAgQYAAAAAI8OFjzty5kyhPlitnzty5ly8ByJxJ85zNmzhz4uTGLZIECQIEHKBC\n5dMnPnxq1Kp1rqnTp00BSJ1K9ZzVq1izYi1XDteCBQLCYsBgp6wdVdiwjVtrzty5t3DPAZhLt67d\nu3jz6t3L95zfv4ADf/o0YACAwwoUoECR4cOHN2+cOTtHubLlywAya958rrPnc+TIZXLgIEAAAw0a\nTJhAgYIEAQIAACDAh485c+dy685drpw5c+eCBwdAvLjxc8iTK1+unBu3SBIkCBBwgAqVT5/48KlR\nq9a57+DDf/8HQL68+XPo06tfr75cOVwLFgiYjwGDnft2VGHDNq6/OYDmzg0keA7AQYQJFS5k2NDh\nQ4jnJE6kKBEcOCsDBgAAIEAADlu2jBkLIUBAgAAHDtD59u3cS5gxXwKgWdPmOZw5z5kz12jBAgEC\nCBgwAAFChAgICBBQoICCESPVqp2jWvWcOW/etm0717UrALBhxZ4jW9bsWbLmzJ06ZWHBAgYMVixZ\nIkTIjh0XrFghR+7cX8CBAQwmXPjcYcSJFZszhw2bDx8MBEwWkMCChSlTXLhYkSKFIUOTokU7V9r0\nOQCpVa9m3dr1a9ixZZ+jXds2bXDgrAwYAACAAAE4bNkyZiz/hAABAQIcOEDn27dz0aVPjw7A+nXs\n57RvP2fOXKMFCwQIIGDAAAQIESIgIEBAgQIKRoxUq3bO/v1z5rx527btHMBzAs8BKGjw4LmEChcy\nTGjO3KlTFhYsYMBgxZIlQoTs2HHBihVy5M6RLGkSAMqUKs+xbOnypTlz2LD58MFAAE4BCSxYmDLF\nhYsVKVIYMjQpWrRzSpeeA+D0KdSoUqdSrWr16rmsWrdu29ajhwAAAAIEePBA0rZtyJA1KVBAgAAA\nAARUqAAN2rm8evcC6Ov377nAgs+ZM4dLggQCiiVIyJGDESNc2rRBg/bryhVRoowZO+fZs7lr17hx\nO2faNIDU/6pXn2vt+jXs1teuCRO2y5q1bNmOqVKVJg0DBg9s2Pj27Rzy5MoBMG/u/Bz06NKlkwMF\nyoGDANoLFAABIkuhQrVqceLEyIsXJUqKVKokTty5+PEB0K9v/z7+/Pr38+9/DuA5gQMFhoMECQKE\nAQsrVEiSpJY1a9my1Vq27NGjCRMEDBhAhgy5cyNJkgRwEmXKcytZshR36hQTJlc8eRo3zpy5czt5\nTpt2586gQeHOFS06bpw5c+eYMgXwFGrUc1OpVrV69ao5c7p0LVjgABOmc2PJlh0LAG1atefYtnXr\nFpcDBwECCBCAoEmTbNnInfP71xs1aqRI7eDCBRu2c4sXA/9w/BhyZMmTKVe2fPlcZs2aw0GCBAHC\nANEVKiRJUsuatWzZai1b9ujRhAkCBgwgQ4bcOd27dwPw/Rv4OeHDh4s7dYoJkyuePI0bZ87cOenT\np027c2fQoHDnuHMfN86cuXPjxwMwfx79OfXr2bd3796cOV26FixwgAnTOf37+esHABCAwIEDzxk8\niBAhLgcOAgQQIABBkybZspE7hzGjN2rUSJHawYULNmznSpYEgDKlypUsW7p8CTOmOXPnao4bt22b\nKQoUECDQMGLEixclStghQ6ZTp23mzJUrd+oUgakiREQ7hzVrVgBcu3o9BzZsWHPJkvXp40uatHLl\nzrl965b/HDlPnjx4ADRunDlz5/r6/QsgsODB5wobPly4nGJz5s45fgzZsTVrCyq/emXO3LnNnDsD\n+Aw69LnRpEuPpkbNSoECAwY0aCBn3LhztGvXNmfNGihQY0iRAgfunHDhAIobP448ufLlzJs7N2fu\nnPRx47ZtM0WBAgIEGkaMePGiRAk7ZMh06rTNnLly5U6dIgBfhIho5+rbtw8gv/795/r7B3hOoLlk\nyfr08SVNWrly5xw+dEiOnCdPHjwAGjfOnLlzHT1+BBBS5MhzJU2eLFlOpTlz51y+hOnSmrUFNV+9\nMmfu3E6ePQH8BBr03FCiRYdSo2alQIEBAxo0kDNu3Dmq/1WrmrNmDRSoMaRIgQN3TqxYAGXNnkWb\nVu1atm3dmjN3Tq45c758QUGBAggQRT9+aNAwYIAABQro0CF3TvG5Y8cGAABgwECuc5UtWwaQWfPm\nc509fy5XDhcuVLJklSt3TvVq1ebMffkiQUIQXbrMmTuXW/duAL19/z4XXPjw4Nq0NTNn7txy5s2X\nZ8u2YoWNUKHMmTuXXft2AN29fz8XXvz48Ny45YAAwYwZTZrGnYMfX/65csSILVpkyps3c+bOATwn\n8ByAggYPIkyocCHDhg7NmTsn0Zw5X76goEABBIiiHz80aBgwQIACBXTokDun8tyxYwMAADBgINe5\nmjZtAv/IqXPnuZ4+f5YrhwsXKlmyypU7p3SpUnPmvnyRICGILl3mzJ3LqnUrgK5ev54LK3ZsWG3a\nmpkzd24t27Zrs2VbscJGqFDmzJ3Lq3cvgL5+/54LLHhwYG7cckCAYMaMJk3jzkGOLPlcOWLEFi0y\n5c2bOXPnPn8GIHo06dKmT6NOrXp1uXLnzJnbtq1KFSY9ekCCNKpNmwMHAgQAYMAADhzRzJkDB+7H\njwAAACBAgMycuXPWrZszB2A79+7nvoMP/50cuV+NGh06RI7cufbugwUrUcKAgQWUKJ3Lr39/fgD+\nAQIQOBDAOYMHEZYrJ0aMk3HjzkWUODHitWskSGDw4+f/XEePHzsCEDmS5DmTJ1GaNGXqyJgxrlwd\nO2buXE2bNrlxq9SnDzNm54AGFQqAaFGjR5EmVbqUaVNzT8+dCxasTZsvunRZs/YNESINGgYMELBg\nAQYMeUaNMmJEgAAAAQKMGVPuXF27dgHk1bv3XF+/fwEXK7ZhgxUrzMQlFudKiBAIEBAgOBIu3DnL\nlzFbBrCZc+dzn0GH/hwnzoNSpc6lVr3anLlGjTp0YMKN2znbt3HbBrCbd+9zv4EH/y1LVhk3bnDh\ncuVKXLly586V69Zt0SIPHqRgw3aOe3fv3AGEFz+efHnz59GnV2+O/blzwYK1afNFly5r1r4hQqRB\nw4AB/wAFLFiAAUOeUaOMGBEgAECAAGPGlDtHsWJFABgzajzHsaPHj8WKbdhgxQozcSjFuRIiBAIE\nBAiOhAt3rqbNmzUB6NzJ85zPn0B9xonzoFSpc0iTKjVnrlGjDh2YcON2rqrVq1UBaN3K9ZzXr2C9\nypJVxo0bXLhcuRJXrty5c+W6dVu0yIMHKdiwndvLt+9eAIADCx5MuLDhw4gTixNXbty4YMFQoQoW\nLRo4cOfAgQsV6soVIhs2HDgwgQQJAgQAAAjw4wc4cOdiy45drhyA27hzn9vNu7dvc+YmTECAYAED\nBgcOUBgxYoPzDZfOSZ9OnTqA69izn9vOvft2U6ZAiP8QIUvWufPoz4HjxStFihEjkp2bT79+fQD4\n8+s/x7+/f4DlypEhI2jMGB8+pkzZxIaNBg0pLlwwYIAAAU/nNG7kyBHAR5AhRY4kWdLkSZTixJUb\nNy5YMFSogkWLBg7cOXDgQoW6coXIhg0HDkwgQYIAAQAAAvz4AQ7cOahRoZYrB8DqVazntG7l2tWc\nuQkTECBYwIDBgQMURozY0HbDpXNx5c6dC8DuXbzn9O7lq9eUKRAiRMiSdc7w4XPgePFKkWLEiGTn\nJE+mTBnAZcyZz23m3LlcOTJkBI0Z48PHlCmb2LDRoCHFhQsGDBAg4Oncbdy5cwPg3dv3b+DBhQ8n\nXjz/XDhzybFho0VLWbly56RPP2fO3Lddu9y4QaFBw4MHKlQQChfu3Hn06c8DYN/e/Tn48eXPh9+t\nmxIlBgoUmDAhCUBFij59kiaN3LmEChcuBODwIcRzEidSpFgjQIAGDZQoAXPlyoULJB48SJIEG7Zz\nKleybAngJcyY52bSrDkTGbIeFSqsWGHCxIMFCxIk8ECCxJIly5ada+r0KVQAUqdSrWr1KtasWrea\nM3fu69dy5c6RLWv2LNq0assCaOv27bm4cufSrVvu7rm8evfy7asXAODAgs8RLmz48LFjefL06LEA\nAoQECXiYMnXuMubMmjMD6Oz587nQokePLmfO3Llz/+bMdfPm7du3ceTInatt+zbu2wB28+7t+zfw\n4MKHEzdn7hxy5OXKnWvu/Dn06NKnOwdg/Tr2c9q3c+/uvRz4c+LHky9vfjyA9OrXn2vv/j38Y8fy\n5OnRYwEECAkS8DBlCuA5gQMJFiQIAGFChecYNnTosJw5c+fOmTPXzZu3b9/GkSN3DmRIkSNFAjB5\nEmVKlStZtnT58lxMmTNp1rR5E6dMADt59jz3E2hQoUOJFjUKFEBSpUvPNXX6FGpUqVOpOgVwFWvW\nc1u5dvX6FWxYsVwBlDV7Fm1atWvZtnV7Dm5cuXPp1rV7Ny4AvXv5nvP7F3BgwYMJF/4LAHFixecY\nN/92/BhyZMmTGwOwfBnzOc2bOXf2/Bl06M0ASJc2fRp1atWrWbc+9xp2bNmzade2DRtAbt27z/X2\n/Rt4cOHDifsGcBx58nPLmTd3/hx6dOnMAVS3fv1cdu3buXf3/h28dgDjyZc3fx59evXr2Z9z/x5+\nfPnz6dd/DwB/fv3n+Pf3D/CcwIEECxo8iFAggIUMG557CDGixIkUK1qECCCjxo3nOnr8CDKkyJEk\nPQI4iTKlypUsW7p8CfOczJk0a9q8iTPnTAA8e/o8BzSo0KFEixo9GhSA0qVMzzl9CjWq1KlUqz4F\ngDWr1nNcu3r9Cjas2LFdAZg9izat2rVs27p9ey7/rty5dOvavYtXLoC9fPue+ws4sODBhAsbBgwg\nseLF5xo7fgw5suTJlB0DuIw587nNnDt7/gw6tGjOAEqbPo06terVrFu7Pgc7tuzZtGvbvh0bgO7d\nvM/5/g08uPDhxIv/BoA8ufJzzJs7fw49uvTpzQFYv479nPbt3Lt7/w4+/HYA5MubP48+vfr17Nu7\nfw8/vvz59Ovbv48/v/79/Pv7BwhA4ECCBQ0eRJhQ4UKGDR0+hBhR4kSKFS1exJhR40aOHT1+BBlS\n5EiSJU2eRJlS5UqWLV2+hBlT5kyaNW3exJlT506ePX3+BBpU6FCiRY3eNGfu3FKmTZ0+ZTpOqjlz\n/+esXsWa1SoArl29mgN7TuxYc+fMmjVnrlw5cuTMnYMbVy7ccOGkSSt3Tu/evQD8/gVsztw5woXP\nmTuXWPFixo0dP04MQPJkyubMncOc+Zy5c507mzN3TvToc+bMeatWzZo1cuTOvYYdWzYA2rVt38ad\nW/du3r3P/QYeXPhw4eLMmTuXXPly5ssBPIce/dx06tWtX8d+Xdx2cee8fwcPQPx48ufMn0efXv16\n9u3PA4AfX/45+vXt38df35w5bteuAQQH7hzBggYPEgSgcCHDhg4fQowoceK5ihYvYsyIsdy5jh4/\nggwJYCTJkudOokypciXLlYMGhQt3bibNmgBu4v/MeW4nz54+fwINKpQngKJGj55LqnQp06ZKzZmz\nduxYuXLnrmLNqvUqgK5ev4INK3Ys2bJmz6FNq3Yt27XlzsGNK3cuXQB27+I9p3cv375+//odNChc\nuHOGDyMGoHgx43OOH0OOLHky5cqPAWDOrPkc586eP4PubM6ctWPHypU7p3o169aqAcCOLXs27dq2\nb+POfW43796+f/Pmxi3cueLGjyNPDmA58+bnnkOPLn06demlFCj49u0c9+7eAYAPL/4c+fLmz6NP\nr359eQDu38M/J38+/fr25xMjtkeVKnPmAJ4TOJBgQYEAECZUuJBhQ4cPIUY8N5FiRYsXKXLjFu7/\nXEePH0GGBDCSZMlzJ1GmVLmSpcpSChR8+3aOZk2bAHDm1HmOZ0+fP4EGFTq0JwCjR5GeU7qUaVOn\nS4kR26NKlTlz57Bm1boVKwCvX8GGFTuWbFmzZ8+lVbuWbdtz2bJduhTsXF27d/HmBbCXb99zfwEH\nFjyYMGBhwgYIEGDO3DnHjyEDkDyZ8jnLlzFn1ryZc+fLAECHFn2OdGnTp1GfkyatSZMh0aKdkz2b\ndm3aAHDn1r2bd2/fv4EHPzeceHHjx89ly3bpUrBzz6FHlz4dQHXr189l176de3fv2oUJGyBAgDlz\n59CnVw+AfXv35+DHlz+ffn379+MD0L+f/zn//wDPCRxIsCBBadKaNBkSLdq5hxAjSowIoKLFixgz\natzIsaPHcyBDihwpcty4Uxw4PHgQQpu2czBjyixXzpy5czhxAtjJs+e5n0CDCh1K9BwmTAMGABAg\nwJy5c1CjSgVAtarVc1izasVKjty5bduIEdOggQECBA4c4ODG7Zzbt3DjwgVAt67dc3jz6t2r15y5\nVREiFCiAQZu2c4gTK16sGIDjx5AjS55MubLly+cya97MOXO3bhAgBBgNAICABQsAAcKGrdy5c+TI\n+erUiRq1c7hxA9jNu/e538CDCx9+zpy5YMFAESL04UOAAAACBDBn7pz169gBaN/O/Zz37+fMmf8T\ntWBBgPMCBABYz779emHCzsmfT7++fAD48+s/x7+/f4DnBA48V67chg0BFCqMcOzYOYgRJU6UCMDi\nRYwZNW7k2NHjx3MhRY4kGbJbNwgQAqwEAEDAggWAAGHDVu7cOXLkfHXqRI3aOaBAAQwlWvTcUaRJ\nlS49Z85csGCgCBH68CFAAAABApgzd87rV7AAxI4le87s2XPmzIlasCDAWwECAMylW3euMGHn9O7l\n21cvAMCBBZ8jXNjwYcLlym3YEMCx4wjHjp2jXNnyZcsANG/m3NnzZ9ChRY8+V9r0adTgwEWIAMD1\na9gBZAdYIEGCCxdJ8uQJF+7c798AhA8nbs7/3DnkyZUvV17u1SsQIDgwYfLhQ4AAAAIEKFfu3Hfw\n4QGMJ1++XLlz6dMHC1YCwHv48AUIMJAiRYAAAPQbMAAOHMBzAgcSLAjgIMKE5xYybOiwXLkLFwBQ\nLFDgwYMUV65w43buI8iQIj8CKGnyJMqUKleybOnyHMyYMmeCAxchAoCcOncG6BlggQQJLlwkyZMn\nXLhzSpUCaOr0qTlz56ZSrWq1arlXr0CA4MCEyYcPAQIACBCgXLlzateyBeD2Ldxy5c7RpRssWAkA\nevfuFSDAQIoUAQIAKGzAADhw5xYzbuwYAOTIks9Rrmz5crlyFy4A6FygwIMHKa5c4cbtHOrU/6pX\nowbg+jXs2LJn065t+/a53Lp38541K0AAAMITJIgQwQEBAgIEAAAwgACBCxe6RItmzty57NkBcO/u\n/Rz48OLHkz/XrJkZM4Y8eWLBYsAAABYsnKtv/359APr38zdnDuA5gQLJkXMiAKGAABAgpEolTtw5\niRLjECCwYIE4cec4dvT4EUBIkSPPlTR5EmWjRgECAACwwI0bX74ArViBBMm3b+d49vT5E0BQoUOJ\nFjV6FGlSpeeYNnXqFBwGDACoAjAQJ06tWqzSpMmQwYABAQQIPHgAJly4c2vZngPwFm7cc3Pp1rV7\n9xw5csGC/XnxwoABAgSilCt3DnFixYgBNP92/LhcuXOTKZ8zd+2aHTtmMmUCB+5caNGh9egZMAAM\nmHOrWbd2DQB2bNnmzJ2zfRt3uXKiDBgAAECAgC/fvo0btyhFigYNbNjgdg56dOnSAVS3fh17du3b\nuXf3fg58ePHiwWHAAAA9AANx4tSqxSpNmgwZDBgQQIDAgwdgwoU7B/CcwIEACho8eC6hwoUMG54j\nRy5YsD8vXhgwQIBAlHLlznn8CNIjgJEkS5Yrdy6lynPmrl2zY8dMpkzgwJ27ifOmHj0DBoABcy6o\n0KFEARg9itScuXNMmzotV06UAQMAAAgQ8OXbt3HjFqVI0aCBDRvczpk9ixYtgLVs27p9Czf/rty5\ndM/ZvYsXb7gOHQIEGDCABDRo5cqJ8+VLhowECQYIEODAQaZx485ZvnwOgObNnM95/gw6tOjP4sTF\nAIAaAAMG5c65fg0bNoDZtGuXK3cut+7duclBg3YuuPDhwoQhQPDnz7nlzJs7BwA9unRz5s5Zv44d\nGrQYBw5MmFCnDrhz5M9ZI0HiwAEbNq6dew8/fnwA9Ovbv48/v/79/PufA3hO4ECCA8N16BAgwIAB\nJKBBK1dOnC9fMmQkSDBAgAAHDjKNG3dO5MhzAEyeRHlO5UqWLV2uFCcuBgCaABgwKHdO506ePAH8\nBBq0XLlzRY0eLUoOGrRzTZ0+FSYMAYI//3/OXcWaVSsArl29mjN3TuxYstCgxThwYMKEOnXAnYN7\nzhoJEgcO2LBx7dxevn37AgAcWPBgwoUNH0ac+Nxixo0XmzPHDAoUBAgWLIiBC9eyZbqMGNGggQAB\nAQQIjBnD7dxq1ufMmQMQW/bsc7Vt38ad23atWgEAAFiwwJy5c8WNH0cOQPly5uXKnYMeXXr0cuXG\njTuXXXv2MmU2bAAF6tx48uXNA0CfXr05c+fcvz9XbtWqDh0iXLkSLNi3b+XMATQnTtypDBls2Ni1\ny9y5hg4fPgQgcSLFihYvYsyoceO5jh4/djRnjhkUKAgQLFgQAxeuZct0GTGiQQMBAgIIEP8YM4bb\nuZ4+z5kzB2Ao0aLnjiJNqnQp0lq1AgAAsGCBOXPnrmLNqhUA165ey5U7J3Ys2bHlyo0bd24t27Vl\nymzYAArUubp27+IFoHcvX3PmzgEOfK7cqlUdOkS4ciVYsG/fypkzJ07cqQwZbNjYtcvcuc6eP38G\nIHo06dKmT6NOrXr1udauX7cWJ04bHz4wYDhwICFFih49LhAgMGBAgAAKgAAZN+4c8+bMy5UDIH06\n9XPWr2PPrv26ChUBECA4J348+fLkAaBPr/4c+/bu37Pv1k2cuHP27e9hwECDhmfPAJo7N5BgwYIA\nECZUeI5hw4bTMGAYMEAHN27nMGIsV87/mjUlUaIYM3aOZEmTJ0kCULmSZUuXL2HGlDnTnLlzN3Ge\nK5csWaVKvBo1okEjQQIDAgQAULoUQIAAI7hxOzeVatVx4wBk1br1XFevX8GGPUeMGAECAsKFO7eW\nbVu3bQHElTv3XF27d/HWbdYsUSJWUaJAgCBgwYJBg7ZtI2fO3DnHjyE7BjCZcuVzlzFjrmXAAAAA\nQ8SJOzd6NDFiYsQ4ePMmXLhzr2HHlv0aQG3bt3Hn1r2bd2/f5sydEz78XLlkySpV4tWoEQ0aCRIY\nECAAQHXrAAIEGMGN2znv38GPGweAfHnz59CnV7+e/TlixAgQEBAu3Dn79/Hnxw+Af3///wDPCRxI\nsKDAZs0SJWIVJQoECAIWLBg0aNs2cubMndvIseNGACBDijxHsmTJWgYMAAAwRJy4czBhEiMmRoyD\nN2/ChTvHs6fPnzwBCB1KtKjRo0iTKl1qzty5p0/NmbtmydKUKVBGjChRYsGCAQECCBBA4MABBgy6\ndIl2rq3bt23NmQNAt67dc3jz6t3L9xwIEAECUDhHuLDhw4gBKF7M+Jzjx5AjOzZnrlYtCAECAAAQ\nAAGCWrXMmTtHurTp0wBSq159rrVr178GDAAAIMCIEcWKYcNWCAECAMATJLBm7Zzx48iTGwfAvLnz\n59CjS59Ovbo5c+eyZzdn7polS1OmQP8ZMaJEiQULBgQIIEAAgQMHGDDo0iXaufv48983Zw6Af4AA\nBA4EcM7gQYQJFZ4DASJAAArnJE6kWNEiAIwZNZ7j2NHjR47mzNWqBSFAAAAAAiBAUKuWOXPnZM6k\nWRPATZw5z+3kyfPXgAEAAAQYMaJYMWzYCiFAAMBpggTWrJ2jWtXqVaoAtG7l2tXrV7BhxY41Z+7c\n2bPlymVjwsSBgwMIECxYgACBAxw41qxRVamSLl3Fio07V9jw4cLlygFg3NjxOciRJU+mfOrBgwAB\nqpzj3NnzZ9AARI8mfc70adSpTZszp0xZigIFAgQY0KCBL1/lyp3j3dv3bwDBhQ8/V9z/uPFoAJQv\nBxAggAIFAQBMnx4gQKlS57Rv595dOwDw4cWPJ1/e/Hn06c+tZ89+XKhQJUogMGDgwIEQIYSRI3fO\nP8Bz58qVI0dOW7ly5xYybLgQAMSIEs9RrGjxokVy5Dxx4ODAgaRzIkeSLGkSAMqUKs+xbOnyJUtz\n5qRJq5UlCwoUCzhwgAbtHNCgQocCBWD0KNJzSpcuFefDBwIEAKZODRAAQICsWQkQUKEiXLhzYseS\nLQvgLNq0ateybev2LdxzcufOHRcqVIkSCAwYOHAgRAhh5MidK1y4XDly5LSVK3fuMeTIjwFQrmz5\nHObMmjdrJkfOEwcODhxIOmf6NOrU/6oBsG7t+hzs2LJnwzZnTpq0WlmyoECxgAMHaNDOES9u/Dhx\nAMqXMz/n/PlzcT58IEAA4Pr1AAEABOjenQABFSrChTtn/jz69ADWs2/v/j38+PLn0zdn7hz+/OfM\nMWOGByCeKhQoLFjAgkW4cwsZnhs37tSpHKBAlSt3DmNGjQA4dvR4DmRIkSNFEiK0hgQJCBAsnXP5\nEmZMmQBo1rR5DmdOnTtxduvmy9ewUaNQoFgQIoQxY+bMnXP6FGpUAFOpVj13FWvWq+bMeatTZ8GC\nCRNEVKmSJYsGBw4MGJAgAdw5uXPp0gVwF29evXv59vX7F7A5c+cIFz5njhkzPHiqUP+gsGABCxbh\nzlW2fG7cuFOncoACVa7cOdGjSQMwfRr1OdWrWbdmTYjQGhIkIECwdA53bt27eQPw/Rv4OeHDiRcX\n3q2bL1/DRo1CgWJBiBDGjJkzdw57du3bAXT3/v1cePHjw5sz561OnQULJkwQUaVKliwaHDgwYECC\nBHDn+Pf3D/CcwHMACho8iDChwoUMGzo8BzGiRIjixB0LEaJAAQYMEn37Zi6kMmU6dAgQEGDAgGzZ\nzrl8CROAzJk0z9m8iTOnTXDgJk3SUqKECBG4zhk9ijSpUgBMmzo9BzWq1KlQxYmjRi3Zo0d37khw\n4GDWrHNky5o9SxaA2rVsz7l9Czf/rty45GzYAABAgIBP5/r6/fsXgODBhAsbPow4seLF5xo7ftxY\nnLhjIUIUKMCAQaJv38x5VqZMhw4BAgIMGJAt27nVrFsDeA079rnZtGvbng0O3KRJWkqUECEC17nh\nxIsbPw4gufLl55o7fw69uThx1Kgle/Tozh0JDhzMmnUuvPjx5MMDOI8+/bn17Nu7f++enA0bAAAI\nEPDpnP79/PkDAAhA4ECCBQ0eRJhQoUJz5s49hBgRIjhwECAMwKhBQ4sWKwgQCBAAwEgBAsSJO5dS\n5UoALV2+PBdT5kya3rx16YIDRwsgQF680HZO6FCiRY0CQJpU6TmmTZ0+dapNW7JS/6VEiEiwYEGz\nZue8fgUb1isAsmXNnkObVu1atmxVqSpQIEAAIebMncObVy9eAH39/gUcWPBgwoUNn0OcWPFixOTI\ndeoEokWLCRNIaNDw4EGAAAXGjDkXWvTo0ABMn0Z9TvVq1qzLBQuWIoUDByd06AAF6txu3r19/z4H\nQPhw4ueMH0eeXHk4SZIkSChgwgQ5cuesX8ee3ToA7t29nwMfXvx48uTHjdOgYcCAPefcv4cPH8B8\n+vXt38efX/9+/uf8AzwncCBBguTIdeoEokWLCRNIaNDw4EGAAAXGjDmncSNHjQA+ggx5biTJkiXL\nBQuWIoUDByd06AAF6hzNmjZv4v88B2Anz57nfgINKnRoOEmSJEgoYMIEOXLnnkKNKvUpgKpWr57L\nqnUr165dx43ToGHAgD3nzqJNmxYA27Zu38KNK3cu3brlyp3Lq3cv373itm1jxgyZNWuvXpkwEWHa\ntHOOH0N2DGAy5crnLmPOnNncpUsUPlMA8eQJNGjnTqNOrXr1OQCuX8M+J3s27dq2y1WpcuDABFSo\nzgEPLny4cADGjyM/p3w58+bOnZMjt2aNAQOMzmHPrl07gO7ev4MPL348+fLmy5U7p349+/bsxW3b\nxowZMmvWXr0yYSLCtGnnAJ4TOJDgOQAHESY8t5Bhw4bmLl2iMJECiCdPoEE7t5H/Y0ePH88BEDmS\n5DmTJ1GmVFmuSpUDByagQnWOZk2bN20C0LmT5zmfP4EGFSqUHLk1awwYYHSOaVOnTgFElTqValWr\nV7Fm1WrO3DmvX8GGFTv23Ldvc6hRO7eWbdu1AODGlXuObl27dyVJunEjRIgatGidEzyYcGHDgwEk\nVrz4XGPHjyFH7jZgAAECWsCBO7eZc2fPnQGEFj36XGnTp1GnTm3N2oMHKVJcOzebdu3aAHDn1r2b\nd2/fv4EHPzeceHHjx5EbnxUs2Dnnz6E7BzCdevVz17Fn116u3KNHT55gMmfuXHnz59GnNw+AfXv3\n5+DHlz+fvq4ECYYMAXeOf3///wDPCRxI8ByAgwgTnlvIsKHDhw/FiXv1ypu3cxgzatwIoKPHjyBD\nihxJsqTJcyhTqlzJsuXKWcGCnZtJs+ZMADhz6jzHs6fPn+XKPXr05Akmc+bOKV3KtKnTpQCiSp16\nrqrVq1iz6kqQYMgQcOfCih1LtiyAs2jTnlvLtq3bt2/FiXv1ypu3c3jz6t0LoK/fv4ADCx5MuLDh\nc4gTK17MuLHjx4kBSJ5M+Zzly5gzYy5X7pznz6BDixYNoLTp0+dSq17NurU5btzOyZ5Nu7bt2QBy\n6959rrfv38CDCx9O3DeA48iTK1/OvLnz59DPSZ9Ovbr169izTwfAvbv3c+DDi/8fL75cuXPo06tf\nz549gPfw45+bT7++/fvmuHE7x7+/f4DnBA4kSBDAQYQJzy1k2NDhQ4gRJTIEUNHiRYwZNW7k2NHj\nOZAhRY4kWdLkyZAAVK5kec7lS5gxZc6kWfMlAJw5dZ7j2dPnT6BBhQ7tCcDoUaTnlC5l2tTpU6hR\nlwKgWtXqVaxZtW7l2vXcV7BhxY4lW9YsWABp1a4919btW7hx5c6l6xbAXbx5z+3l29fvX8CBBfMF\nUNjw4XOJFS9m3NjxY8iKAUymXNnyZcyZNW/mfM7zZ9ChRY8mXfozANSpVZ9j3dr1a9ixZc9uDcD2\nbdzndO/m3dv3b+DBdwMgXtz/+DnkyZUvZ97c+fPkAKRPp17d+nXs2bVvP9fd+3fw4cWPJ+8dwHn0\n6c+tZ9/e/Xv48eWzB1Df/v1z+fXv59/fP8BzAgcSLFgQAMKECs8xbOjwIcSIEic2BGDxIsaMGjdy\n7Ojx47mQIkeSLGnyJEqRAFaybHnuJcyYMmfSrGkTJoCcOnee6+nzJ9CgQocS9QngKNKk55Yyber0\nKdSoUpkCqGr1KtasWrdy7er1K9iwYseSLWv2LNq0ateybev2Ldy4cufSrWv3Lt68evfy7ev3L+DA\nggcTLmz4MOLEihczbuz4MeTIkidTrmz5MubMmjdz7uz5M+jQokeTLm36NOrUQ6pXs27t+jXs2LJn\n065t+zbu3Lp38+7t+zfw4MKHEy9u/Djy5MqXM2/u/Dn06NKnU69u/Tr27Nq3c+/u/Tv48OJxBwQA\nIfkECAoAAAAsAAAAACABIAGH/////v7+/f39/Pz8+/v7+vr6+fn5+Pj49/f39vb29fX18/Pz8vLy\n8fHx8PDw7+/v7u7u7e3t7Ozs6+vr6urq6enp6Ojo5+fn5ubm5eXl4+Pj4uLi4eHh4ODg39/f3t7e\n3d3d3Nzc29vb2tra2dnZ2NjY19fX1tbW1dXV09PT0tLS0dHR0NDQz8/Pzs7Ozc3NzMzMy8vLysrK\nycnJyMjIx8fHxsbGxcXFw8PDwsLCwcHBwMDAv7+/vr6+vb29vLy8u7u7urq6ubm5uLi4t7e3tra2\ntbW1s7OzsrKysbGxsLCwr6+vrq6ura2trKysq6urqqqqqampqKiop6enpqampaWlo6OjoqKioaGh\noKCgn5+fnp6enZ2dm5ubmpqamZmZmJiYl5eXlpaWlZWVk5OTkpKSkZGRkJCQj4+Pjo6OjY2NjIyM\ni4uLioqKiYmJiIiIh4eHhoaGhYWFg4ODgoKCgYGBgICAf39/fn5+fX19fHx8e3t7eXl5eHh4d3d3\ndnZ2dXV1dHR0c3NzcXFxcHBwb29vbm5ubW1tbGxsa2traWlpaGhoZ2dnZmZmZWVlZGRkY2NjYWFh\nYGBgX19fXl5eXV1dXFxcW1tbWVlZWFhYV1dXVlZWVVVVVFRUU1NTUVFRUFBQT09PTk5OTU1NTExM\nS0tLSUlJSEhIR0dHRkZGRUVFREREQ0NDQUFBQEBAPz8/Pj4+PDw8Ozs7Ojo6ODg4Nzc3NjY2NDQ0\nMzMzMjIyMDAwLy8vLi4uLCwsKysrKioqKCgoJycnJiYmJCQkIyMjIiIiICAgHx8fHh4eHR0dHBwc\nGxsbGhoaGRkZGBgYFxcXFhYWFRUVFBQUExMTEhISEREREBAQDw8PDg4ODQ0NDAwMCwsLCgoKCQkJ\nCAgIBwcHBgYGBQUFBAQEAwMDAgICAQEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n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\nrF7FmlXrVq5drwIAG1ZsuXLmzJ5Fm1btWrZtzQGAG1duuXLm7NotV87cXr59/f4FHJgvAMKFDZtD\nnNhcuXLmHD+GHFnyZMqPAVzGnFnzZs6dPX8GHVr0aNKlTZ9GnVr1atatXb+GHVv2bNq1bd/GnVv3\nbt69ff8GHlz4cOLFjR9Hnlz5cubNnT+HHl36dOrVrV/Hnl37du7dvX8HH178ePLlzZ9Hn179evbt\n3b+HH1/+fPr17d/Hn1//fv79/QMEIHAgwYIGDyJMqHAhw4YOH0KMKHEixYoWL2LMqHEjx44eP4IM\nKXIkyZImT6JMqXIly5YuX8KMKXMmzZo2b+IxzKlzJ8+ePn8CDSp0KNGiRo8iTap0KdOmTp9CjSp1\nKtWqVq9izap1K9euXr+CDfs0IAAh+QQICgAAACwAAAAAIAEgAYf////+/v79/f38/Pz7+/v6+vr5\n+fn4+Pj39/f29vb19fXz8/Py8vLx8fHw8PDv7+/u7u7t7e3s7Ozr6+vq6urp6eno6Ojn5+fm5ubl\n5eXj4+Pi4uLh4eHg4ODf39/e3t7d3d3c3Nzb29va2trZ2dnY2NjX19fW1tbV1dXT09PS0tLR0dHQ\n0NDPz8/Ozs7Nzc3MzMzLy8vKysrJycnIyMjHx8fGxsbFxcXDw8PCwsLBwcHAwMC/v7++vr69vb28\nvLy7u7u6urq5ubm4uLi3t7e2tra1tbWzs7OysrKxsbGwsLCvr6+urq6tra2srKyrq6uqqqqpqamo\nqKinp6empqalpaWjo6OioqKhoaGgoKCenp6dnZ2cnJybm5uampqZmZmYmJiXl5eWlpaVlZWTk5OS\nkpKRkZGQkJCPj4+Ojo6NjY2MjIyLi4uKioqJiYmIiIiHh4eGhoaFhYWDg4OCgoKBgYGAgIB/f39+\nfn59fX18fHx7e3t5eXl4eHh3d3d2dnZ1dXV0dHRzc3NxcXFwcHBvb29ubm5tbW1sbGxra2tpaWlo\naGhnZ2dmZmZlZWVkZGRjY2NhYWFgYGBfX19eXl5dXV1cXFxbW1tZWVlYWFhXV1dWVlZVVVVUVFRT\nU1NRUVFQUFBPT09OTk5NTU1MTExLS0tJSUlISEhHR0dGRkZFRUVERERDQ0NBQUFAQEA/Pz8+Pj48\nPDw7Ozs6Ojo4ODg3Nzc2NjY0NDQzMzMyMjIwMDAvLy8uLi4sLCwrKysqKiooKCgnJycmJiYkJCQj\n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\nKjTHsKHDhxAhlitnytSGDdvMadzIkSOAjyBDihxJsqTJkyjNqVzJsqXLlzBjrgRAs6ZNczhz6tyJ\nc9w4c0CDCh1KtKg5AEiTKjXHtKnTp1DN6dIlREg4c1izat3KFYDXr2DNiR1LtqzZs+aYMQsWzJzb\nt3DjAphLt67du3jz6t3L15zfv4ADCx5MuPBfAIgTKzbHuLHjx5AjS57cGIDly5jNad7MubPnzdiw\nmRtNurTp0+YAqF7N2pzr17Bjy55trlw5ceLM6d7NuzeA38CDCx9OvLjx48jNKV/OvLnz59CjLwdA\nvbp1c9iza9/Ovbv379kBiNMfT96c+fPo06s/jw2buffw48ufbw6A/fv4zenfz7+/f4DmBA4UWK6c\nOHHmFC5k2BDAQ4gRJU6kWNHiRYwZNW7k2NHjR5AhRY4kWdLkSZQpVa5k2dLlS5gxZc6kWdPmTZw5\nde7k2dPnT6BBhQ4lWtToUaRJlS5l2tTpU6hRpU6lWtXqVaxZtW7l2tXrV7BhxY4lW9bsWbRp1a5l\n29btW7hx5c6lW9fuXbx59e7l29fvX8CBBQ8mXNjwYcSJFS9m3NjxY8iRJU+mXNnyZcyZNW/mLDcg\nACH5BAgKAAAALAAAAAAgASABh/////7+/v39/fz8/Pv7+/r6+vn5+fj4+Pf39/b29vX19fPz8/Ly\n8vHx8fDw8O/v7+7u7u3t7ezs7Ovr6+rq6unp6ejo6Ofn5+bm5uXl5ePj4+Li4uHh4eDg4N/f397e\n3t3d3dzc3Nvb29ra2tnZ2djY2NfX19bW1tXV1dPT09LS0tHR0dDQ0M/Pz87Ozs3NzczMzMvLy8rK\nysnJycjIyMfHx8bGxsXFxcPDw8LCwsHBwcDAwL+/v76+vr29vby8vLu7u7q6urm5ubi4uLe3t7a2\ntrW1tbOzs7KysrCwsK+vr66urq2traysrKurq6qqqqmpqaioqKenp6ampqWlpaOjo6KioqGhoaCg\noJ+fn56enp2dnZycnJubm5qampmZmZiYmJeXl5WVlZOTk5KSkpGRkZCQkI+Pj46Ojo2NjYyMjIuL\ni4qKiomJiYiIiIeHh4aGhoWFhYODg4KCgoGBgYCAgH5+fnx8fHt7e3l5eXh4eHd3d3Z2dnV1dXR0\ndHNzc3FxcXBwcG9vb25ubm1tbWxsbGtra2lpaWhoaGZmZmVlZWRkZGNjY2FhYWBgYF9fX15eXl1d\nXVxcXFtbW1lZWVhYWFdXV1ZWVlVVVVRUVFNTU1FRUVBQUE9PT05OTk1NTUxMTEtLS0lJSUhISEdH\nR0ZGRkVFRURERENDQ0FBQUBAQD8/Pz4+Pjw8PDs7Ozo6Ojg4ODc3NzY2NjQ0NDMzMzIyMjAwMC8v\nLy4uLiwsLCsrKyoqKigoKCcnJyYmJiQkJCMjIyIiIiAgIB8fHx4eHh0dHRwcHBsbGxoaGhkZGRgY\nGBcXFxYWFhUVFRQUFBMTExISEhERERAQEA8PDw4ODg0NDQwMDAsLCwoKCgkJCQgICAcHBwYGBgUF\nBQQEBAMDAwICAgEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAj/AAEIHEiwoMGD\nCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzps2bOHPq\n3Mmzp8+fQIMKHUq0qNGjSJMqXcq0qdOnUKNKnUq1qtWrWLNq3cq1q9evYMOKHUu2rNmzaNOqXcu2\nrdu3cOPKnUu3rt27ePPq3cu3r9+/gAMLHky4sOHDiBMrXsy4sVpxkCNLnky5suXLkQFo3sxZnOfP\noEOLHk269GcAqFOrFse6tevXsGPLnt0agO3buMOFE8e7t+/fwIMLHy4OgPHjyJMrX868ufPn4qJL\nn069uvXr2KUD2M69u7jv4MOL/x9Pvrx58ADSq18vrr379/Djy59P3z2A+/jzi9vPv79/gOIEDiRY\n0ODBggAULmTY0OFDiBElThRX0eJFjBk1buRoEcBHkCHFjSRZ0uRJlClVkgTQ0uVLcTFlzqRZ0+ZN\nnDIB7OTZU9xPoEGFDiVa1ChQAEmVLmXa1OlTqFGliqNa1epVrFm1bq0KwOtXsOLEjiVb1uxZtGnH\nAmDb1q04uHHlzqVb1+7duAD07uUrzu9fwIEFDyZc+C8AxIkVL2bc2PFjyJHFTaZc2fJlzJk1UwbQ\n2fNncaFFjyZdWhw4cOJUr2bd2vVqALFlzxZX2/Zt3Ll17+ZtG8Bv4MHFDSde3P/4ceTJlRMH0Nz5\nc+jRpU+nXt26OOzZtW/n3t379+wAxI8nL878efTp1YsDB07ce/jx5c+HD8D+ffzi9O/n398/QHEC\n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\nx8fGxsbFxcXDw8PCwsLBwcHAwMC/v7++vr68vLy7u7u6urq5ubm4uLi3t7e1tbWzs7OysrKxsbGw\nsLCvr6+urq6tra2srKyrq6uqqqqpqamoqKinp6empqalpaWjo6OioqKhoaGgoKCfn5+enp6dnZ2c\nnJybm5uampqZmZmYmJiXl5eWlpaVlZWTk5OSkpKRkZGQkJCPj4+Ojo6NjY2MjIyLi4uKioqJiYmI\niIiHh4eGhoaFhYWDg4OCgoKBgYGAgIB/f39+fn59fX18fHx7e3t5eXl4eHh3d3d2dnZ1dXV0dHRz\nc3NxcXFwcHBvb29ubm5tbW1sbGxra2tpaWloaGhnZ2dmZmZlZWVkZGRjY2NhYWFgYGBfX19eXl5d\nXV1cXFxbW1tZWVlYWFhXV1dWVlZVVVVUVFRTU1NRUVFQUFBPT09OTk5NTU1MTExLS0tJSUlISEhH\nR0dGRkZFRUVERERDQ0NBQUFAQEA/Pz8+Pj48PDw7Ozs6Ojo4ODg3Nzc2NjY0NDQzMzMyMjIwMDAv\nLy8uLi4sLCwrKysqKiooKCgnJycmJiYkJCQjIyMiIiIgICAfHx8eHh4dHR0cHBwbGxsaGhoZGRkY\nGBgXFxcWFhYVFRUUFBQTExMSEhIREREQEBAPDw8ODg4NDQ0MDAwLCwsKCgoJCQkICAgHBwcGBgYF\nBQUEBAQDAwMCAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI/wABCBxIsKDBgwgTKlzIsKHD\nhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CD\nCh1KtKjRo0iTKl3KtKnTp1CjSp1KtarVq1izat3KtavXr2DDih1LtqzZs2jTql3Ltq3bt3Djyp1L\nt67du3jz6t3Lt6/fv4ADCx5MuLDhw4gTK17MuLHacpAjRyZXrrLly5gza95cGYDnz6DLiR5NurTp\n06hTjwbAurXrcrBjy55Nu7bt27EB6N7Nmxy5csCDCx9OvLjx4+UAKF/OvLnz59CjS59errr169iz\na9/O3TqA7+DDl/8bT768+fPo06snD6C9+/fl4sufT7++/fv45QPYz79/OYDlBA4kWNDgQYQJBQJg\n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\n","text/plain":[""]},"metadata":{"tags":[]},"execution_count":23}]},{"metadata":{"id":"6EEG-wePkmJQ","colab_type":"text"},"cell_type":"markdown","source":["**Download animated gif**\n","\n","Uncomment the code below to download an animated gif from Colab:"]},{"metadata":{"id":"4UJjSnIMOzOJ","colab_type":"code","colab":{}},"cell_type":"code","source":["#from google.colab import files\n","#files.download('dcgan.gif')"],"execution_count":0,"outputs":[]},{"metadata":{"id":"k6qC-SbjK0yW","colab_type":"text"},"cell_type":"markdown","source":["## Learn more about GANs\n"]},{"metadata":{"id":"xjjkT9KAK6H7","colab_type":"text"},"cell_type":"markdown","source":["Now that you have learned how to generate new images (MNIST digits) with deep convolutional GANs, here are a few suggested next steps:\n","\n","* Tweak the code in this tutorial to see different effects.\n","* Try out this tutorial on a different dataset, for example the Large-scale Celeb Faces Attributes (CelebA) dataset ([available on Kaggle](https://www.kaggle.com/jessicali9530/celeba-dataset/home)).\n","* Learn more about GANs - see below the learning resources.\n","\n","** Deep Generative Models and GANs**\n","\n","GANs is a type of deep generative models and DCGAN is just one type of the GANs. \n","* MIT [Intro to Deep Learning](http://introtodeeplearning.com/) lecture on **Deep Generative Models** has a great intro to generative models as well as GANs. ([video](https://youtu.be/JVb54xhEw6Y) | [slides](http://introtodeeplearning.com/materials/2018_6S191_Lecture4.pdf))\n","* Stanford CS 231N lecture 12 **Generative Models** on PixelRNN/CNN, \n","VAE and GANs. ([slides](http://cs231n.stanford.edu/slides/2018/cs231n_2018_lecture12.pdf))\n","* This Github has a good [collection](https://github.com/wiseodd/generative-models) of GANs and generative models. \n","\n","**GANs research papers:**\n","* The original [GANs](https://arxiv.org/abs/1406.2661) paper.\n","* DCGAN paper: [Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks](https://arxiv.org/abs/1511.06434).\n","\n","**GANs tutorials**\n","\n","* [NIPS 2016 Tutorial: Generative Adversarial Networks](https://arxiv.org/abs/1701.00160) - a bit dated but great explanation on what/why generative models, what are GANs and how they compare to other generative models.\n","* Here is a site with excellent tutorials on GANs by **Computer Vision and Pattern Recognition** - [CVPR 2018 Tutorial on GANs](https://sites.google.com/view/cvpr2018tutorialongans/).\n"]}]} \ No newline at end of file diff --git a/tensorflow/contrib/eager/python/examples/generative_examples/gans_diagram.png b/tensorflow/contrib/eager/python/examples/generative_examples/gans_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..b715bd83ef117641c6429e0ac173dbe9b8d5fd88 GIT binary patch literal 63265 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 literal 0 HcmV?d00001 -- GitLab From 7f46f0f040133e18fda1a7054c9640d486ef8aa1 Mon Sep 17 00:00:00 2001 From: Clayne Robison Date: Wed, 17 Oct 2018 06:54:39 -0700 Subject: [PATCH 0196/1825] [Intel MKL] Update README.md links to point to 1.11 whls --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c8d4d4ef0..98ef4b3c8d 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ Build Type **IBM ppc64le GPU** Nightly | [![Build Status](https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Nightly_Artifact/badge/icon)](https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Nightly_Artifact/) | [Nightly](https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Nightly_Artifact/) **IBM ppc64le GPU** Stable Release | [![Build Status](https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Release_Build/badge/icon)](https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Release_Build/) | [Release](https://powerci.osuosl.org/job/TensorFlow_PPC64LE_GPU_Release_Build/) **Linux CPU with Intel® MKL-DNN** Nightly | [![Build Status](https://tensorflow-ci.intel.com/job/tensorflow-mkl-linux-cpu/badge/icon)](https://tensorflow-ci.intel.com/job/tensorflow-mkl-linux-cpu/) | [Nightly](https://tensorflow-ci.intel.com/job/tensorflow-mkl-build-whl-nightly/) -**Linux CPU with Intel® MKL-DNN** Python 2.7
**Linux CPU with Intel® MKL-DNN** Python 3.5
**Linux CPU with Intel® MKL-DNN** Python 3.6 | [![Build Status](https://tensorflow-ci.intel.com/job/tensorflow-mkl-build-release-whl/badge/icon)](https://tensorflow-ci.intel.com/job/tensorflow-mkl-build-release-whl/lastStableBuild) | [1.10.0 py2.7](https://storage.googleapis.com/intel-optimized-tensorflow/tensorflow-1.10.0-cp27-cp27mu-linux_x86_64.whl)
[1.10.0 py3.5](https://storage.googleapis.com/intel-optimized-tensorflow/tensorflow-1.10.0-cp35-cp35m-linux_x86_64.whl)
[1.10.0 py3.6](https://storage.googleapis.com/intel-optimized-tensorflow/tensorflow-1.10.0-cp36-cp36m-linux_x86_64.whl) +**Linux CPU with Intel® MKL-DNN** Python 2.7
**Linux CPU with Intel® MKL-DNN** Python 3.4
**Linux CPU with Intel® MKL-DNN** Python 3.5
**Linux CPU with Intel® MKL-DNN** Python 3.6 | [![Build Status](https://tensorflow-ci.intel.com/job/tensorflow-mkl-build-release-whl/badge/icon)](https://tensorflow-ci.intel.com/job/tensorflow-mkl-build-release-whl/lastStableBuild) | [1.11.0 py2.7](https://storage.googleapis.com/intel-optimized-tensorflow/tensorflow-1.11.0-cp27-cp27mu-linux_x86_64.whl)
[1.11.0 py3.4](https://storage.googleapis.com/intel-optimized-tensorflow/tensorflow-1.11.0-cp34-cp34m-linux_x86_64.whl)
[1.11.0 py3.5](https://storage.googleapis.com/intel-optimized-tensorflow/tensorflow-1.11.0-cp35-cp35m-linux_x86_64.whl)
[1.11.0 py3.6](https://storage.googleapis.com/intel-optimized-tensorflow/tensorflow-1.11.0-cp36-cp36m-linux_x86_64.whl) ## For more information * [TensorFlow Website](https://www.tensorflow.org) -- GitLab From 684f5bf198b866fe77a0635a2871dc73303e7b47 Mon Sep 17 00:00:00 2001 From: Roger Iyengar Date: Wed, 17 Oct 2018 10:51:50 -0400 Subject: [PATCH 0197/1825] Ran clang-format from version 3.6.0 of LLVM --- .../tflitecamerademo/ImageClassifier.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java b/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java index 39057aa776..b186b3ff87 100644 --- a/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java +++ b/tensorflow/contrib/lite/java/demo/app/src/main/java/com/example/android/tflitecamerademo/ImageClassifier.java @@ -84,27 +84,20 @@ public abstract class ImageClassifier { private static final float FILTER_FACTOR = 0.4f; private PriorityQueue> sortedLabels = - new PriorityQueue<>( - RESULTS_TO_SHOW, - new Comparator>() { - @Override - public int compare(Map.Entry o1, Map.Entry o2) { - return (o1.getValue()).compareTo(o2.getValue()); - } - }); + new PriorityQueue<>(RESULTS_TO_SHOW, new Comparator>() { + @Override + public int compare(Map.Entry o1, Map.Entry o2) { + return (o1.getValue()).compareTo(o2.getValue()); + } + }); /** Initializes an {@code ImageClassifier}. */ ImageClassifier(Activity activity) throws IOException { tfliteModel = loadModelFile(activity); tflite = new Interpreter(tfliteModel, tfliteOptions); labelList = loadLabelList(activity); - imgData = - ByteBuffer.allocateDirect( - DIM_BATCH_SIZE - * getImageSizeX() - * getImageSizeY() - * DIM_PIXEL_SIZE - * getNumBytesPerChannel()); + imgData = ByteBuffer.allocateDirect(DIM_BATCH_SIZE * getImageSizeX() * getImageSizeY() + * DIM_PIXEL_SIZE * getNumBytesPerChannel()); imgData.order(ByteOrder.nativeOrder()); filterLabelProbArray = new float[FILTER_STAGES][getNumLabels()]; Log.d(TAG, "Created a Tensorflow Lite Image Classifier."); -- GitLab From 1cfd1df084a31fa228923a284511b682eb4cd69a Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Wed, 17 Oct 2018 15:53:24 +0100 Subject: [PATCH 0198/1825] Cleanup empty lines --- tensorflow/core/graph/graph_constructor.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/tensorflow/core/graph/graph_constructor.cc b/tensorflow/core/graph/graph_constructor.cc index 4e0dcbee4f..0ddab8264f 100644 --- a/tensorflow/core/graph/graph_constructor.cc +++ b/tensorflow/core/graph/graph_constructor.cc @@ -975,8 +975,6 @@ Status GraphConstructor::Convert() { node_def = &original_node_def; } - - DCHECK_EQ(node_def->input_size(), input_already_exists.size()); TF_RETURN_IF_ERROR(ValidateColocationConstraints(*node_def)); for (int i = 0; i < node_def->input_size(); ++i) { -- GitLab From 0575b7f5d11b2784babca472eef20fe2c568f117 Mon Sep 17 00:00:00 2001 From: Evgeniy Polyakov Date: Wed, 17 Oct 2018 16:06:02 +0100 Subject: [PATCH 0199/1825] Added unit test to check if requested device is indeed propogated into the node --- .../core/graph/graph_constructor_test.cc | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tensorflow/core/graph/graph_constructor_test.cc b/tensorflow/core/graph/graph_constructor_test.cc index 73142ebde7..b5e1519bc5 100644 --- a/tensorflow/core/graph/graph_constructor_test.cc +++ b/tensorflow/core/graph/graph_constructor_test.cc @@ -3205,6 +3205,30 @@ TEST_F(GraphConstructorTest, ImportGraphDef_ValidateColationConstraints) { TF_EXPECT_OK(ImportGraphDef(options, def, &graph_, nullptr)); } +TEST_F(GraphConstructorTest, ImportGraphDef_ValidateDefaultDevice) { + std::string gdef_ascii( + R"EOF( + node { name: 'test_input' op: 'TestInput' } + node { name: 'test_op' op: 'TestMul' input: [ 'test_input:0', 'test_input:1' ] } + )EOF"); + + GraphDef gdef; + CHECK(protobuf::TextFormat::ParseFromString(gdef_ascii, &gdef)); + + ImportGraphDefOptions options; + std::string dev = "/gpu:13"; + options.default_device = dev; + options.return_nodes = std::vector{"test_input", "test_op"}; + + ImportGraphDefResults res; + + TF_EXPECT_OK(ImportGraphDef(options, gdef, &graph_, NULL, &res)); + EXPECT_EQ(res.return_nodes.size(), options.return_nodes.size()); + for (auto node: res.return_nodes) { + EXPECT_EQ(node->requested_device(), dev); + } +} + TEST_F(GraphConstructorTest, ImportGraphDef_UnknownOps) { const string pb_ascii = "node { name: 'op_from_contrib' op: 'OpFromContrib'}"; // Try load twice to check for two parts of the error message. We cannot check -- GitLab From 5674c9423f0732e6ab7b4cc428f730ce7bd3e857 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 09:04:45 -0700 Subject: [PATCH 0200/1825] Internal change PiperOrigin-RevId: 217527648 --- tensorflow/core/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 4f95f207ad..7d864d434d 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -593,6 +593,7 @@ cc_library( "//tensorflow/core/platform/default/build_config:other", "//tensorflow/core/platform/default/build_config:platformlib", "//tensorflow/core/platform/default/build_config:port", + "@com_google_absl//absl/flags:flag", ], ) @@ -2194,6 +2195,7 @@ cc_library( ":lib_proto_parsing", ":abi", ":core_stringpiece", + "@com_google_absl//absl/flags:flag", "//third_party/eigen3", "//tensorflow/core/platform/default/build_config:platformlib", "@snappy", -- GitLab From 4513f1910bf435c4cc46d0f0dfb3e0641286ce21 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 09:51:13 -0700 Subject: [PATCH 0201/1825] Raise ValueError in tf.einsum() when passed unsupported input. The case where an axis appears more than once for a single input was listed in the docstring as unsupported, but the code didn't raise an exception in this case, and instead returned an incorrect result. This change also fixes an unrelated bug in the docstring. PiperOrigin-RevId: 217535799 --- tensorflow/python/ops/special_math_ops.py | 8 +++++++- tensorflow/python/ops/special_math_ops_test.py | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index e44bafedfc..cb417e4eb5 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -182,7 +182,6 @@ def einsum(equation, *inputs, **kwargs): * Ellipses (subscripts like `ij...,jk...->ik...`) * Subscripts where an axis appears more than once for a single input (e.g. `ijj,k->ik`). - * Subscripts that are summed across multiple inputs (e.g., `ij,ij,jk->ik`). Args: equation: a `str` describing the contraction, in the same format as @@ -238,6 +237,13 @@ def einsum(equation, *inputs, **kwargs): output_axis_labels = ''.join( sorted(ax for ax in indices if counts[ax] == 1)) + for a in axis_labels: + for input_labels in input_axis_labels: + if input_labels.count(a) > 1: + raise ValueError( + 'Subscript not supported: an axis appears more than once: %s' % + input_labels) + for a in axis_labels: input_count = sum(1 for s in input_axis_labels if a in s) if input_count > 2 and a not in output_axis_labels: diff --git a/tensorflow/python/ops/special_math_ops_test.py b/tensorflow/python/ops/special_math_ops_test.py index d2f6b47697..7438cdb3f1 100644 --- a/tensorflow/python/ops/special_math_ops_test.py +++ b/tensorflow/python/ops/special_math_ops_test.py @@ -311,6 +311,11 @@ class EinsumTest(test.TestCase): invalid1='value1', invalid2='value2') + def test_repeated_axis_single_input(self): + x = array_ops.placeholder(dtypes.float32, shape=[2, 2]) + with self.assertRaises(ValueError): + _ = special_math_ops.einsum('ii->', x) + def test_dim_mismatch(self): for axes, input_shapes in self.dim_mismatch_cases: inputs = [ -- GitLab From af7ebf45751df025bf5561ddb992b9c7a91b4201 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Wed, 17 Oct 2018 10:01:58 -0700 Subject: [PATCH 0202/1825] Automated rollback of commit 5674c9423f0732e6ab7b4cc428f730ce7bd3e857 PiperOrigin-RevId: 217537594 --- tensorflow/core/BUILD | 2 -- 1 file changed, 2 deletions(-) diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 7d864d434d..4f95f207ad 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -593,7 +593,6 @@ cc_library( "//tensorflow/core/platform/default/build_config:other", "//tensorflow/core/platform/default/build_config:platformlib", "//tensorflow/core/platform/default/build_config:port", - "@com_google_absl//absl/flags:flag", ], ) @@ -2195,7 +2194,6 @@ cc_library( ":lib_proto_parsing", ":abi", ":core_stringpiece", - "@com_google_absl//absl/flags:flag", "//third_party/eigen3", "//tensorflow/core/platform/default/build_config:platformlib", "@snappy", -- GitLab From db46af2f820242a0249022135b15fab738bc7865 Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Wed, 17 Oct 2018 10:27:19 -0700 Subject: [PATCH 0203/1825] Enabling tf.function on one more benchmark. PiperOrigin-RevId: 217542911 --- .../eager/python/examples/resnet50/resnet50_test.py | 3 +-- tensorflow/python/eager/def_function.py | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/eager/python/examples/resnet50/resnet50_test.py b/tensorflow/contrib/eager/python/examples/resnet50/resnet50_test.py index e406aee29d..fb81979d7b 100644 --- a/tensorflow/contrib/eager/python/examples/resnet50/resnet50_test.py +++ b/tensorflow/contrib/eager/python/examples/resnet50/resnet50_test.py @@ -267,8 +267,7 @@ class ResNet50Benchmarks(tf.test.Benchmark): apply_grads = apply_gradients if defun: model.call = tfe.function(model.call) - # TODO(apassos) enable tf.function here - apply_grads = tfe.defun(apply_gradients) + apply_grads = tfe.function(apply_gradients) num_burn = 3 num_iters = 10 diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 63f8e698a8..022c8685a8 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -29,7 +29,9 @@ from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import math_ops from tensorflow.python.ops import resource_variable_ops from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables from tensorflow.python.training.checkpointable import base as checkpointable +from tensorflow.python.util import nest class UnliftedInitializerVariable(resource_variable_ops.ResourceVariable): @@ -178,12 +180,15 @@ def _defun_with_scope(scope, fn, input_signature): return function_lib.defun(wrapped_fn, input_signature=input_signature) +# TODO(apassos) there should be an easier way to call a concrete defun. def _call_concrete(fn, args, unused_kwargs): """Calls the given concrete function with only the tensor arguments.""" def inner(): # TODO(apassos) figure out what to do with kwargs and concrete functions. - return fn(*[x for x in args if isinstance(x, ops.Tensor)]) + return fn(*[x if isinstance(x, ops.Tensor) else x.handle + for x in nest.flatten(args) + if isinstance(x, (ops.Tensor, variables.Variable))]) return inner @@ -254,7 +259,7 @@ class PolymorphicFunction(object): elif self._stateful_fn is not None: # In this case we have not created variables on the first call. So we can # run the first trace but we should fail if variables are created. - results = self._first_trace(*args, **kwds) + results = self._stateful_fn(*args, **kwds) if self._created_variables: raise ValueError("Creating variables on a non-first call to a function" " decorated with tf.function.") -- GitLab From 9a20e435c5b4b941fd7014342b7a359e5c6cdd6b Mon Sep 17 00:00:00 2001 From: Jing Li Date: Wed, 17 Oct 2018 10:33:20 -0700 Subject: [PATCH 0204/1825] - Support bfloat16 in Keras TPU. - Explicitly put optimizer parameter variables on TPU. PiperOrigin-RevId: 217544154 --- .../contrib/tpu/python/tpu/keras_support.py | 22 ++++++++++++++----- .../tpu/python/tpu/keras_tpu_variables.py | 3 ++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/keras_support.py b/tensorflow/contrib/tpu/python/tpu/keras_support.py index 9d7b894717..083b65a8da 100644 --- a/tensorflow/contrib/tpu/python/tpu/keras_support.py +++ b/tensorflow/contrib/tpu/python/tpu/keras_support.py @@ -280,9 +280,9 @@ def _cross_replica_concat(tensor, core_id, num_cores, name): """ input_dtype = tensor.dtype - if input_dtype not in [dtypes.float32, dtypes.int32]: - raise TypeError('For model replication, only (float32 and int32) is ' - 'supported for model outputs and targets. Got {} for ' + if input_dtype not in [dtypes.bfloat16, dtypes.float32, dtypes.int32]: + raise TypeError('For model replication, only (bfloat16, float32 and int32) ' + 'is supported for model outputs and targets. Got {} for ' '{}.'.format(input_dtype, name)) batch_size = tensor.shape[0] @@ -362,7 +362,7 @@ def _replicated_optimizer(opt): return KerasCrossShardOptimizer(opt) -def _clone_optimizer(optimizer, config=None): +def _clone_optimizer(optimizer, config=None, worker_name=None): """Returns a cloned optimizer with the provided optimizer.config or config.""" if not isinstance(optimizer, keras_optimizers.Optimizer): # In the first call to tpu_model(model), Keras may not have wrapped the TF @@ -377,7 +377,10 @@ def _clone_optimizer(optimizer, config=None): if config is None: config = optimizer.get_config() logging.info('Cloning %s %s', optimizer.__class__.__name__, config) - return optimizer.__class__.from_config(config) + with ops.device( + '%s/device:CPU:0' % ('/job:%s' % worker_name if worker_name else '')): + # Explicitly put optimizer parameter variables on TPU worker. + return optimizer.__class__.from_config(config) class TPURewriteContext(object): @@ -956,7 +959,8 @@ class TPUFunction(object): self._tpu_assignment.num_towers): if not self._cloned_optimizer: self._cloned_optimizer = _clone_optimizer( - self.model.cpu_optimizer) + self.model.cpu_optimizer, + worker_name=self._tpu_assignment.worker_name) self._cloned_model = models.clone_model(self.model) @@ -973,6 +977,12 @@ class TPUFunction(object): name='model output ({})'.format(o.name)) for o in self._cloned_model.outputs ] + # Recast all low precision outputs back to float32 since we only + # casted the inputs to bfloat16 and not targets. This is done so + # that we can preserve precision when calculating the loss value. + if new_outputs and new_outputs[0].dtype == dtypes.bfloat16: + new_outputs = [ + math_ops.cast(o, dtypes.float32) for o in new_outputs] self._cloned_model.outputs = new_outputs tpu_targets = [ _cross_replica_concat( diff --git a/tensorflow/contrib/tpu/python/tpu/keras_tpu_variables.py b/tensorflow/contrib/tpu/python/tpu/keras_tpu_variables.py index 004b1012e5..28d3a93851 100644 --- a/tensorflow/contrib/tpu/python/tpu/keras_tpu_variables.py +++ b/tensorflow/contrib/tpu/python/tpu/keras_tpu_variables.py @@ -33,6 +33,7 @@ from tensorflow.python.framework import ops from tensorflow.python.keras import backend from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import gen_resource_variable_ops +from tensorflow.python.ops import math_ops from tensorflow.python.ops import resource_variable_ops from tensorflow.python.ops import variable_scope @@ -227,7 +228,7 @@ class ReplicatedVariable(object): return self._primary_var._dense_var_to_tensor(dtype, name, as_ref) # pylint: enable=protected-access if dtype is not None and dtype != self.dtype: - return NotImplemented + return math_ops.cast(self._read_variable_op(), dtype) if as_ref: return self.handle else: -- GitLab From f43a352c12c08d7b1f65e7c1bbd149dbd7904d4f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 11:31:52 -0700 Subject: [PATCH 0205/1825] [XLA] More improvements in module proto verification. Issues fixed in this CL: * HloInputOutputAliasConfig::Verify should preverify param_number read from the proto before using it * hlo verifier should reject sparse layouts * hlo verifier should reject instructions with called_computations if they don't support it * verifier should check that element types of Broadcast and Reshape instructions match that of their operands * InferWindowOutputShape should check that the resulting shape is valid, i.e. it has a valid element type and size does not overflow int64 * InferConvolveShape should require that input and output have same number of spatial dimensions * literals should not be supported for shapes with OPAQUE sub-shapes * ShapeUtil::IndexIsValid should check that indices are non-negative * SparseIndexArray should support index count equal to max_indices PiperOrigin-RevId: 217556707 --- tensorflow/compiler/xla/literal.cc | 3 + .../xla/service/gpu/while_transformer_test.cc | 4 +- .../service/hlo_input_output_alias_config.cc | 3 +- .../compiler/xla/service/hlo_verifier.cc | 58 +++++++++++++------ .../compiler/xla/service/hlo_verifier.h | 7 +++ .../compiler/xla/service/shape_inference.cc | 18 ++++-- tensorflow/compiler/xla/shape_util.cc | 26 ++++++--- tensorflow/compiler/xla/shape_util.h | 10 +++- tensorflow/compiler/xla/sparse_index_array.cc | 2 +- 9 files changed, 96 insertions(+), 35 deletions(-) diff --git a/tensorflow/compiler/xla/literal.cc b/tensorflow/compiler/xla/literal.cc index 656ce720a1..8a8f49ccd0 100644 --- a/tensorflow/compiler/xla/literal.cc +++ b/tensorflow/compiler/xla/literal.cc @@ -283,6 +283,9 @@ Status MutableLiteralBase::CopyElementFrom(const LiteralSlice& src_literal, if (!proto.has_shape()) { return InvalidArgument("LiteralProto has no shape"); } + if (ShapeUtil::HasPrimitiveType(proto.shape(), OPAQUE)) { + return InvalidArgument("Literal shape cannot include OPAQUE sub-shape"); + } if (!LayoutUtil::HasLayout(proto.shape())) { return InvalidArgument("LiteralProto has no layout"); } diff --git a/tensorflow/compiler/xla/service/gpu/while_transformer_test.cc b/tensorflow/compiler/xla/service/gpu/while_transformer_test.cc index 9a61f8ac5a..926b59a1b8 100644 --- a/tensorflow/compiler/xla/service/gpu/while_transformer_test.cc +++ b/tensorflow/compiler/xla/service/gpu/while_transformer_test.cc @@ -69,8 +69,10 @@ class WhileTransformerTest : public HloTestBase { auto data = builder.AddInstruction(HloInstruction::CreateGetTupleElement( data_shape_, loop_state, data_tuple_index)); // Use 'induction_variable' in computation with no path to output tuple. + auto cast = builder.AddInstruction(HloInstruction::CreateBitcastConvert( + ShapeUtil::MakeShape(F32, {}), induction_variable)); auto update = builder.AddInstruction( - HloInstruction::CreateBroadcast(data_shape_, induction_variable, {})); + HloInstruction::CreateBroadcast(data_shape_, cast, {})); auto add1 = builder.AddInstruction(HloInstruction::CreateBinary( data_shape_, HloOpcode::kAdd, data, update)); // Create output Tuple. 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 8128fad07c..450c620576 100644 --- a/tensorflow/compiler/xla/service/hlo_input_output_alias_config.cc +++ b/tensorflow/compiler/xla/service/hlo_input_output_alias_config.cc @@ -157,10 +157,11 @@ Status HloInputOutputAliasConfig::Verify(const HloModule& module) const { const ShapeIndex& param_index) -> Status { const HloInstruction* root = entry->root_instruction(); + TF_RET_CHECK(0 <= param_number); + TF_RET_CHECK(entry->num_parameters() > param_number); const Shape& param_shape = entry->parameter_instruction(param_number)->shape(); const Shape& output_shape = root->shape(); - TF_RET_CHECK(entry->num_parameters() > param_number); TF_RET_CHECK(ShapeUtil::IndexIsValid(param_shape, param_index)); TF_RET_CHECK(ShapeUtil::IndexIsValid(output_shape, output_index)); diff --git a/tensorflow/compiler/xla/service/hlo_verifier.cc b/tensorflow/compiler/xla/service/hlo_verifier.cc index a1cb60a049..e955b905ad 100644 --- a/tensorflow/compiler/xla/service/hlo_verifier.cc +++ b/tensorflow/compiler/xla/service/hlo_verifier.cc @@ -27,12 +27,42 @@ limitations under the License. namespace xla { +Status VerifyNotSparse(const Shape& shape) { + return ShapeUtil::ForEachSubshapeWithStatus( + shape, [](const Shape& subshape, const ShapeIndex&) -> Status { + if (LayoutUtil::IsSparseArray(subshape)) { + return InternalError("Sparse arrays are not yet fully supported: %s", + ShapeUtil::HumanStringWithLayout(subshape)); + } + return Status::OK(); + }); +} + +bool IsCallerInstruction(HloInstruction* hlo) { + switch (hlo->opcode()) { + case HloOpcode::kCall: + case HloOpcode::kConditional: + case HloOpcode::kWhile: + case HloOpcode::kCrossReplicaSum: + case HloOpcode::kMap: + case HloOpcode::kReduce: + case HloOpcode::kReduceWindow: + case HloOpcode::kScatter: + case HloOpcode::kSelectAndScatter: + case HloOpcode::kFusion: + return true; + default: + return false; + } +} + Status ShapeVerifier::Preprocess(HloInstruction* hlo) { - if (LayoutUtil::IsSparseArray(hlo->shape())) { - return InternalError("Sparse arrays are not yet fully supported: %s", - hlo->ToString()); + if (!hlo->called_computations().empty() && !IsCallerInstruction(hlo)) { + return InternalError( + "Called computations specified for non-caller instruction %s", + hlo->ToString()); } - return Status::OK(); + return VerifyNotSparse(hlo->shape()); } static Status CheckOperandCount(const HloInstruction* hlo, int expected) { @@ -351,7 +381,7 @@ Status ShapeVerifier::HandleBroadcast(HloInstruction* broadcast) { // ShapeInference method. Check the output shape explicitly. const Shape& operand_shape = broadcast->operand(0)->shape(); // Check for mixed precision. - TF_RETURN_IF_ERROR(CheckShape(broadcast, broadcast->shape())); + TF_RET_CHECK(SameElementType(broadcast->shape(), operand_shape)); TF_RET_CHECK(ShapeUtil::Rank(operand_shape) == broadcast->dimensions().size()); for (int64 operand_dimension = 0; @@ -369,9 +399,10 @@ Status ShapeVerifier::HandleBroadcast(HloInstruction* broadcast) { Status ShapeVerifier::HandleReshape(HloInstruction* reshape) { TF_RETURN_IF_ERROR(CheckOperandCount(reshape, 1)); // Check for mixed precision. - TF_RETURN_IF_ERROR(CheckShape(reshape, reshape->shape())); + const Shape& operand_shape = reshape->operand(0)->shape(); + TF_RET_CHECK(SameElementType(reshape->shape(), operand_shape)); TF_RET_CHECK(ShapeUtil::ElementsIn(reshape->shape()) == - ShapeUtil::ElementsIn(reshape->operand(0)->shape())); + ShapeUtil::ElementsIn(operand_shape)); return Status::OK(); } @@ -902,11 +933,7 @@ Status CheckEntryComputationLayout(const HloModule& module) { TF_RETURN_IF_ERROR( ShapeUtil::ValidateShapeWithOptionalLayout(result_layout.shape())); - if (LayoutUtil::IsSparseArray(result_layout.shape())) { - return Unimplemented( - "Sparse arrays are not yet fully supported in program result shape: %s", - ShapeUtil::HumanStringWithLayout(result_layout.shape())); - } + TF_RETURN_IF_ERROR(VerifyNotSparse(result_layout.shape())); if (!ShapeUtil::Compatible(computation->root_instruction()->shape(), result_layout.shape())) { @@ -928,12 +955,7 @@ Status CheckEntryComputationLayout(const HloModule& module) { const HloInstruction* parameter = computation->parameter_instruction(i); TF_RETURN_IF_ERROR( ShapeUtil::ValidateShapeWithOptionalLayout(layout.parameter_shape(i))); - if (LayoutUtil::IsSparseArray(layout.parameter_shape(i))) { - return Unimplemented( - "Sparse arrays are not yet fully supported " - "in program parameter shape: %s", - ShapeUtil::HumanStringWithLayout(layout.parameter_shape(i))); - } + TF_RETURN_IF_ERROR(VerifyNotSparse(layout.parameter_shape(i))); if (!ShapeUtil::Compatible(parameter->shape(), layout.parameter_shape(i))) { return InternalError( "Shape of the entry computation parameter %d is %s should be " diff --git a/tensorflow/compiler/xla/service/hlo_verifier.h b/tensorflow/compiler/xla/service/hlo_verifier.h index e1f3402465..5e65d76877 100644 --- a/tensorflow/compiler/xla/service/hlo_verifier.h +++ b/tensorflow/compiler/xla/service/hlo_verifier.h @@ -123,6 +123,13 @@ class ShapeVerifier : public DfsHloVisitor { : ShapeUtil::HumanString(s); } + // Helpers that switch on allow_mixed_precision_. + bool SameElementType(const Shape& a, const Shape& b) { + return allow_mixed_precision_ + ? ShapeUtil::SameElementTypeIgnoringFpPrecision(a, b) + : ShapeUtil::SameElementType(a, b); + } + // Checks that the given operand of the given instruction is of type TOKEN. Status CheckIsTokenOperand(const HloInstruction* instruction, int64 operand_no); diff --git a/tensorflow/compiler/xla/service/shape_inference.cc b/tensorflow/compiler/xla/service/shape_inference.cc index 25afc23e5b..2e02b256cb 100644 --- a/tensorflow/compiler/xla/service/shape_inference.cc +++ b/tensorflow/compiler/xla/service/shape_inference.cc @@ -207,7 +207,7 @@ StatusOr InferWindowOutputShape(const Shape& base_shape, padded_dilated_base, dilated_window, dim.stride()); } - return ShapeUtil::MakeShape(element_type, output_dimensions); + return ShapeUtil::MakeValidatedShape(element_type, output_dimensions); } } // namespace @@ -1580,8 +1580,16 @@ ShapeInference::InferDegenerateDimensionBroadcastShape(HloOpcode operation, dnums.kernel_spatial_dimensions_size()) { return InvalidArgument( "Both arguments to convolution must have same number of dimensions.\n" - "Window: %s", - window.DebugString()); + "Numbers: %s", + dnums.DebugString()); + } + + if (dnums.input_spatial_dimensions_size() != + dnums.output_spatial_dimensions_size()) { + return InvalidArgument( + "Both input and output of convolution must have same number of " + "dimensions.\nNumbers: %s", + dnums.DebugString()); } const int num_spatial_dims = dnums.input_spatial_dimensions_size(); @@ -1600,8 +1608,8 @@ ShapeInference::InferDegenerateDimensionBroadcastShape(HloOpcode operation, } if (ShapeUtil::Rank(rhs) != num_dims) { return InvalidArgument( - "The RHS argument to a convolution should have rank %d; lhs: %s.", - num_dims, ShapeUtil::HumanString(lhs)); + "The RHS argument to a convolution should have rank %d; rhs: %s.", + num_dims, ShapeUtil::HumanString(rhs)); } TF_DCHECK_OK(ShapeUtil::ValidateShapeWithOptionalLayout(lhs)); TF_DCHECK_OK(ShapeUtil::ValidateShapeWithOptionalLayout(rhs)); diff --git a/tensorflow/compiler/xla/shape_util.cc b/tensorflow/compiler/xla/shape_util.cc index f55508f8e6..43b10a4af4 100644 --- a/tensorflow/compiler/xla/shape_util.cc +++ b/tensorflow/compiler/xla/shape_util.cc @@ -216,9 +216,14 @@ StatusOr MakeShapeWithLayoutInternal( /* static */ Shape ShapeUtil::MakeShape(PrimitiveType element_type, absl::Span dimensions) { + return MakeValidatedShape(element_type, dimensions).ValueOrDie(); +} + +/* static */ StatusOr ShapeUtil::MakeValidatedShape( + PrimitiveType element_type, absl::Span dimensions) { CHECK(IsArrayPrimitiveType(element_type)); Shape result; - PopulateShape(element_type, dimensions, &result); + TF_RETURN_IF_ERROR(PopulateShape(element_type, dimensions, &result)); return result; } @@ -256,16 +261,16 @@ ShapeUtil::MakeShapeWithDescendingLayoutAndSamePhysicalLayout( return MakeShapeWithDescendingLayout(shape.element_type(), dims); } -/* static */ void ShapeUtil::PopulateShape(PrimitiveType element_type, - absl::Span dimensions, - Shape* shape) { +/* static */ Status ShapeUtil::PopulateShape(PrimitiveType element_type, + absl::Span dimensions, + Shape* shape) { shape->Clear(); shape->set_element_type(element_type); for (int64 dimension : dimensions) { shape->add_dimensions(dimension); } LayoutUtil::SetToDefaultLayout(shape); - TF_DCHECK_OK(ValidateShape(*shape)); + return ValidateShape(*shape); } /* static */ Shape ShapeUtil::MakeTupleShape(absl::Span shapes) { @@ -786,6 +791,9 @@ StatusOr ParseShapeStringInternal(absl::string_view* s) { return byte_size; } else if (shape.element_type() == TOKEN) { return 0; + } else if (shape.element_type() == OPAQUE) { + CHECK_GT(pointer_size, 0); + return pointer_size; } LOG(FATAL) << PrimitiveType_Name(shape.element_type()) << " primitive type has no definitive size"; @@ -981,7 +989,7 @@ StatusOr ParseShapeStringInternal(absl::string_view* s) { ShapeIndexView index) { const Shape* subshape = &shape; for (auto i : index) { - if (!IsTuple(*subshape) || i >= subshape->tuple_shapes_size()) { + if (!IsTuple(*subshape) || i >= subshape->tuple_shapes_size() || i < 0) { return false; } subshape = &subshape->tuple_shapes(i); @@ -1609,7 +1617,11 @@ ShapeUtil::DimensionsUnmodifiedByReshape(const Shape& input_shape, Shape output_shape_with_layout = MakeShapeWithLayout( output_shape.element_type(), AsInt64Slice(output_shape.dimensions()), output_layout); - CHECK(ReshapeIsBitcast(input_shape, output_shape_with_layout)); + CHECK(ReshapeIsBitcast(input_shape, output_shape_with_layout)) + << "reshape is not a bitcast for input_shape: " + << ShapeUtil::HumanStringWithLayout(input_shape) + << " and output_shape_with_layout: " + << ShapeUtil::HumanStringWithLayout(output_shape_with_layout); return output_shape_with_layout; } diff --git a/tensorflow/compiler/xla/shape_util.h b/tensorflow/compiler/xla/shape_util.h index 51cedce7f0..191ab04759 100644 --- a/tensorflow/compiler/xla/shape_util.h +++ b/tensorflow/compiler/xla/shape_util.h @@ -365,6 +365,12 @@ class ShapeUtil { static Shape MakeShape(PrimitiveType element_type, absl::Span dimensions); + // Constructs a new shape with the given element type and sequence of + // dimensions. Method checks if the element type is valid and the shape's + // size fits in std::numeric_limits::max(). + static StatusOr MakeValidatedShape(PrimitiveType element_type, + absl::Span dimensions); + // Creates a Shape with element type corresponding to T and the given // dimensions template @@ -396,8 +402,8 @@ class ShapeUtil { const Shape& shape); // As MakeShape, but the object to write to is passed in. - static void PopulateShape(PrimitiveType element_type, - absl::Span dimensions, Shape* shape); + static Status PopulateShape(PrimitiveType element_type, + absl::Span dimensions, Shape* shape); // Validates that the provided shape satisfies invariants. static Status ValidateShape(const Shape& shape); diff --git a/tensorflow/compiler/xla/sparse_index_array.cc b/tensorflow/compiler/xla/sparse_index_array.cc index 1c135dda86..a40bb7875e 100644 --- a/tensorflow/compiler/xla/sparse_index_array.cc +++ b/tensorflow/compiler/xla/sparse_index_array.cc @@ -29,7 +29,7 @@ SparseIndexArray::SparseIndexArray(int64 max_indices, int64 rank, CHECK_GT(rank_, 0); CHECK_EQ(indices_.size() % rank_, 0) << "indices_.size(): " << indices_.size() << ", rank_: " << rank_; - CHECK_LT(index_count(), max_indices_); + CHECK_LE(index_count(), max_indices_); } SparseIndexArray::SparseIndexArray(int64 max_indices, int64 rank, -- GitLab From af653ab648cf1e8069ed34127a2070d6a8cae57a Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Wed, 17 Oct 2018 11:45:55 -0700 Subject: [PATCH 0206/1825] Handle extrapolation only case gracefully --- .../core/kernels/crop_resize_bilinear_core.h | 394 +++++++++--------- 1 file changed, 208 insertions(+), 186 deletions(-) diff --git a/tensorflow/core/kernels/crop_resize_bilinear_core.h b/tensorflow/core/kernels/crop_resize_bilinear_core.h index 62c275d4cc..6167cafea2 100644 --- a/tensorflow/core/kernels/crop_resize_bilinear_core.h +++ b/tensorflow/core/kernels/crop_resize_bilinear_core.h @@ -3616,33 +3616,53 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { x1_(flip_x ? out_width - 1 - min_ix : max_ix), y0_(flip_y ? out_height - 1 - max_iy : min_iy), y1_(flip_y ? out_height - 1 - min_iy : max_iy) { - // copy xs values, but filter out the following: - // xs[].lower == xs[].upper AND xs[].lerp == 0 - // xs[].lower == xs[].upper AND xs[].lerp == 1 - assert(min_ix_ <= max_ix_); - xs_ = new CachedInterpolation[max_ix_ - min_ix_ + 1]; - for (int i = min_ix_; i <= max_ix_; ++i) { - int ix = i - min_ix_; - int xs_lower = xs[ix].lower / channels_; - int xs_upper = xs[ix].upper / channels_; - if (xs_lower == xs_upper) { - if (xs[ix].lerp == 0.0f && xs_lower + 1 < in_width) { - // upper weight is zero - xs_upper = xs_lower + 1; - } else if (xs[ix].lerp == 1.0f && xs_upper - 1 >= 0) { - // lower weight is zero - xs_lower = xs_upper - 1; - } + if (min_ix_ <= max_ix_ && min_iy_ <= max_iy_) { + // copy xs values, but filter out the following: + // xs[].lower == xs[].upper AND xs[].lerp == 0 + // xs[].lower == xs[].upper AND xs[].lerp == 1 + xs_ = new CachedInterpolation[max_ix_ - min_ix_ + 1]; + for (int i = min_ix_; i <= max_ix_; ++i) { + int ix = i - min_ix_; + int xs_lower = xs[ix].lower / channels_; + int xs_upper = xs[ix].upper / channels_; + if (xs_lower == xs_upper) { + if (xs[ix].lerp == 0.0f && xs_lower + 1 < in_width) { + // upper weight is zero + xs_upper = xs_lower + 1; + } else if (xs[ix].lerp == 1.0f && xs_upper - 1 >= 0) { + // lower weight is zero + xs_lower = xs_upper - 1; + } + } + xs_[ix].lower = xs_lower * channels_; + xs_[ix].upper = xs_upper * channels_; + xs_[ix].lerp = xs[ix].lerp; } - xs_[ix].lower = xs_lower * channels_; - xs_[ix].upper = xs_upper * channels_; - xs_[ix].lerp = xs[ix].lerp; + _u_min_val = std::numeric_limits::min(); + _u_max_val = std::numeric_limits::max(); + _f_min_val = static_cast(_u_min_val); + _f_max_val = static_cast(_u_max_val); + Configure_(); + } else { + // crop region outside of input image. + // extrapolation only. + general_x_ = NULL; + load1_x_ = NULL; + load2_x_ = NULL; + load4_x_ = NULL; + load8_x_ = NULL; + load1_offsets_ = NULL; + load2_offsets_ = NULL; + load4_offsets_ = NULL; + load8_offsets_ = NULL; + load1_shuffle_masks_ = NULL; + load2_shuffle_masks_ = NULL; + load1_mmxs_lerp_ = NULL; + load2_mmxs_lerp_ = NULL; + load4_mmxs_lerp_ = NULL; + load8_mmxs_lerp_ = NULL; + xs_ = NULL; } - _u_min_val = std::numeric_limits::min(); - _u_max_val = std::numeric_limits::max(); - _f_min_val = static_cast(_u_min_val); - _f_max_val = static_cast(_u_max_val); - Configure_(); } ~CropResizeCastImage() { if (general_x_ != NULL) delete[] general_x_; @@ -3803,168 +3823,170 @@ void CropResizeCastImage::Resize(const T* input_image, U* output_image) { } } // interpolation region - int y = y0_; - for (y = y0_; y + 1 <= y1_; y += 2) { - const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; - const float yA_lerp = ys_[iyA].lerp; - const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); - const T* ysA_input_lower_ptr = - input_image + ys_[iyA].lower * in_width_ * channels_; - const T* ysA_input_upper_ptr = - input_image + ys_[iyA].upper * in_width_ * channels_; - U* ysA_output_ptr = output_image + y * out_width_ * channels_; - const int iyB = - flip_y_ ? out_height_ - 1 - min_iy_ - (y + 1) : (y + 1) - min_iy_; - const float yB_lerp = ys_[iyB].lerp; - const __m128 ysB_lerp = _mm_set1_ps(yB_lerp); - const T* ysB_input_lower_ptr = - input_image + ys_[iyB].lower * in_width_ * channels_; - const T* ysB_input_upper_ptr = - input_image + ys_[iyB].upper * in_width_ * channels_; - U* ysB_output_ptr = output_image + (y + 1) * out_width_ * channels_; - if (channels_ == 1) { - this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load1_1ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_1ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_1ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_1ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - } else if (channels_ == 2) { - this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load1_2ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_2ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_2ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_2ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - } else if (channels_ == 3) { - this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load1_3ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_3ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_3ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_3ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - } else if (channels_ == 4) { - this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load1_4ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_4ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_4ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_4ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - } else { - assert(false); + if (min_ix_ <= max_ix_ && min_iy_ <= max_iy_) { + int y = y0_; + for (y = y0_; y + 1 <= y1_; y += 2) { + const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; + const float yA_lerp = ys_[iyA].lerp; + const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); + const T* ysA_input_lower_ptr = + input_image + ys_[iyA].lower * in_width_ * channels_; + const T* ysA_input_upper_ptr = + input_image + ys_[iyA].upper * in_width_ * channels_; + U* ysA_output_ptr = output_image + y * out_width_ * channels_; + const int iyB = + flip_y_ ? out_height_ - 1 - min_iy_ - (y + 1) : (y + 1) - min_iy_; + const float yB_lerp = ys_[iyB].lerp; + const __m128 ysB_lerp = _mm_set1_ps(yB_lerp); + const T* ysB_input_lower_ptr = + input_image + ys_[iyB].lower * in_width_ * channels_; + const T* ysB_input_upper_ptr = + input_image + ys_[iyB].upper * in_width_ * channels_; + U* ysB_output_ptr = output_image + (y + 1) * out_width_ * channels_; + if (channels_ == 1) { + this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + } else if (channels_ == 2) { + this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + } else if (channels_ == 3) { + this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + } else if (channels_ == 4) { + this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + } else { + assert(false); + } } - } - for (; y <= y1_; ++y) { - const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; - const float yA_lerp = ys_[iyA].lerp; - const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); - const T* ysA_input_lower_ptr = - input_image + ys_[iyA].lower * in_width_ * channels_; - const T* ysA_input_upper_ptr = - input_image + ys_[iyA].upper * in_width_ * channels_; - U* ysA_output_ptr = output_image + y * out_width_ * channels_; - if (channels_ == 1) { - this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - } else if (channels_ == 2) { - this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - } else if (channels_ == 3) { - this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - } else if (channels_ == 4) { - this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - } else { - assert(false); + for (; y <= y1_; ++y) { + const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; + const float yA_lerp = ys_[iyA].lerp; + const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); + const T* ysA_input_lower_ptr = + input_image + ys_[iyA].lower * in_width_ * channels_; + const T* ysA_input_upper_ptr = + input_image + ys_[iyA].upper * in_width_ * channels_; + U* ysA_output_ptr = output_image + y * out_width_ * channels_; + if (channels_ == 1) { + this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + } else if (channels_ == 2) { + this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + } else if (channels_ == 3) { + this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + } else if (channels_ == 4) { + this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + } else { + assert(false); + } } } } -- GitLab From 75154000c2edf8d67500afba12f0a7b5c410c12f Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 12:02:20 -0700 Subject: [PATCH 0207/1825] Internal change. PiperOrigin-RevId: 217562080 --- tensorflow/contrib/lite/kernels/lstm_eval.cc | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/lstm_eval.cc b/tensorflow/contrib/lite/kernels/lstm_eval.cc index 5b7951a931..f2ba7b46d9 100644 --- a/tensorflow/contrib/lite/kernels/lstm_eval.cc +++ b/tensorflow/contrib/lite/kernels/lstm_eval.cc @@ -715,8 +715,14 @@ TfLiteStatus EvalFloat( TfLiteTensor* activation_state, TfLiteTensor* cell_state, TfLiteTensor* output) { TF_LITE_ASSERT(input->dims->size >= 2 && input->dims->size <= 3); - const int max_time = (input->dims->size == 2) ? 1 : input->dims->data[0]; - const int n_batch = input->dims->data[input->dims->size - 2]; + int max_time, n_batch; + if (input->dims->size == 3) { + max_time = (time_major) ? input->dims->data[0] : input->dims->data[1]; + n_batch = (time_major) ? input->dims->data[1] : input->dims->data[0]; + } else { + max_time = 1; + n_batch = input->dims->data[0]; + } const int n_input = input->dims->data[input->dims->size - 1]; const int aux_input_size = (aux_input) ? aux_input->dims->data[aux_input->dims->size - 1] : 0; @@ -821,6 +827,9 @@ TfLiteStatus EvalFloat( // backwards. const int t_rel = forward_sequence ? t : max_time - t - 1; const float* input_ptr = input->data.f + t_rel * input_step; + if (aux_input) { + aux_input_ptr = aux_input->data.f + t_rel * input_step; + } float* output_ptr_time = output->data.f + t_rel * output_step + output_offset; @@ -874,9 +883,15 @@ TfLiteStatus EvalHybrid( TfLiteTensor* cell_state_quantized, TfLiteTensor* output_state, TfLiteTensor* cell_state, TfLiteTensor* output) { TF_LITE_ASSERT(input->dims->size >= 2 && input->dims->size <= 3); - const int max_time = (input->dims->size == 2) ? 1 : input->dims->data[0]; - const int n_batch = input->dims->data[input->dims->size - 2]; const int n_input = input->dims->data[input->dims->size - 1]; + int max_time, n_batch; + if (input->dims->size == 2) { + max_time = 1; + n_batch = input->dims->data[0]; + } else { + max_time = (time_major) ? input->dims->data[0] : input->dims->data[1]; + n_batch = (time_major) ? input->dims->data[1] : input->dims->data[0]; + } const int aux_input_size = (aux_input) ? aux_input->dims->data[aux_input->dims->size - 1] : 0; // n_cell and n_output will be the same size when there is no projection. @@ -1078,6 +1093,9 @@ TfLiteStatus EvalHybrid( // backwards. const int t_rel = forward_sequence ? t : max_time - t - 1; const float* input_ptr = input->data.f + t_rel * input_step; + if (aux_input) { + aux_input_ptr = aux_input->data.f + t_rel * input_step; + } float* output_ptr = output->data.f + t_rel * output_step + output_offset; -- GitLab From 3cd85a0c541dcf3b86e5da5a20b1b4680b6a865a Mon Sep 17 00:00:00 2001 From: Akshay Modi Date: Wed, 17 Oct 2018 12:18:53 -0700 Subject: [PATCH 0208/1825] Stop creating unnecessary tensor_specs, and use caching IsTensor check. Before: entry { name: "MicroBenchmarks.benchmark_defun_with_signature" iters: 30000 wall_time: 96.1242675781 extras { key: "examples_per_sec" value { double_value: 10403.2002032 } } } entry { name: "MicroBenchmarks.benchmark_defun_with_signature_and_kwargs" iters: 30000 wall_time: 100.377964973 extras { key: "examples_per_sec" value { double_value: 9962.34582226 } } } After: entry { name: "MicroBenchmarks.benchmark_defun_with_signature" iters: 30000 wall_time: 70.356965065 extras { key: "examples_per_sec" value { double_value: 14213.2338863 } } } entry { name: "MicroBenchmarks.benchmark_defun_with_signature_and_kwargs" iters: 30000 wall_time: 83.8630994161 extras { key: "examples_per_sec" value { double_value: 11924.1955874 } } } PiperOrigin-RevId: 217565341 --- tensorflow/python/eager/function.py | 7 ++----- tensorflow/python/util/util.i | 3 +++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 5fd49dd979..6d34cffdf6 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -1307,14 +1307,11 @@ class PolymorphicFunction(object): except (ValueError, TypeError): raise ValueError("Structure of Python function inputs does not match " "input_signature.") - if any(not isinstance(arg, ops.Tensor) for arg in flat_inputs): + if any(not pywrap_tensorflow.IsTensor(arg) for arg in flat_inputs): raise ValueError("When input_signature is provided, all inputs to " "the Python function must be Tensors.") - tensor_specs = [ - tensor_spec.TensorSpec.from_tensor(tensor) for tensor in flat_inputs - ] if any(not spec.is_compatible_with(other) - for spec, other in zip(self._flat_input_signature, tensor_specs)): + for spec, other in zip(self._flat_input_signature, flat_inputs)): raise ValueError("Python inputs incompatible with input_signature: " "inputs (%s), input_signature (%s)" % (str(inputs), str(self._input_signature))) diff --git a/tensorflow/python/util/util.i b/tensorflow/python/util/util.i index 3c0ec87fa4..4d34d61eee 100644 --- a/tensorflow/python/util/util.i +++ b/tensorflow/python/util/util.i @@ -31,6 +31,9 @@ limitations under the License. %unignore tensorflow::swig::RegisterType; %noexception tensorflow::swig::RegisterType; +%unignore tensorflow::swig::IsTensor; +%noexception tensorflow::swig::IsTensor; + %feature("docstring") tensorflow::swig::IsSequence """Returns a true if its input is a collections.Sequence (except strings). -- GitLab From cf2644ad058c899402a0170a9617fe4cec24b8fe Mon Sep 17 00:00:00 2001 From: Eugene Brevdo Date: Wed, 17 Oct 2018 12:33:39 -0700 Subject: [PATCH 0209/1825] Add proper weights properties for MultiRNNCell. PiperOrigin-RevId: 217567838 --- .../python/kernel_tests/core_rnn_cell_test.py | 8 +++++-- tensorflow/python/ops/rnn_cell_impl.py | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/rnn/python/kernel_tests/core_rnn_cell_test.py b/tensorflow/contrib/rnn/python/kernel_tests/core_rnn_cell_test.py index 572df58e52..245fa68eae 100644 --- a/tensorflow/contrib/rnn/python/kernel_tests/core_rnn_cell_test.py +++ b/tensorflow/contrib/rnn/python/kernel_tests/core_rnn_cell_test.py @@ -757,9 +757,10 @@ class RNNCellTest(test.TestCase): "root", initializer=init_ops.constant_initializer(0.5)): x = array_ops.zeros([1, 2]) m = array_ops.zeros([1, 4]) - _, ml = rnn_cell_impl.MultiRNNCell( + multi_rnn_cell = rnn_cell_impl.MultiRNNCell( [rnn_cell_impl.GRUCell(2) for _ in range(2)], - state_is_tuple=False)(x, m) + state_is_tuple=False) + _, ml = multi_rnn_cell(x, m) sess.run([variables_lib.global_variables_initializer()]) res = sess.run(ml, { x.name: np.array([[1., 1.]]), @@ -767,6 +768,9 @@ class RNNCellTest(test.TestCase): }) # The numbers in results were not calculated, this is just a smoke test. self.assertAllClose(res, [[0.175991, 0.175991, 0.13248, 0.13248]]) + self.assertEqual(len(multi_rnn_cell.weights), 2 * 4) + self.assertTrue( + [x.dtype == dtypes.float32 for x in multi_rnn_cell.weights]) def testMultiRNNCellWithStateTuple(self): with self.cached_session() as sess: diff --git a/tensorflow/python/ops/rnn_cell_impl.py b/tensorflow/python/ops/rnn_cell_impl.py index dd4f3d7a99..8eb7d93c01 100644 --- a/tensorflow/python/ops/rnn_cell_impl.py +++ b/tensorflow/python/ops/rnn_cell_impl.py @@ -1466,6 +1466,30 @@ class MultiRNNCell(RNNCell): # presumably does not contain TensorArrays or anything else fancy return super(MultiRNNCell, self).zero_state(batch_size, dtype) + @property + def trainable_weights(self): + if not self.trainable: + return [] + weights = [] + for cell in self._cells: + if isinstance(cell, base_layer.Layer): + weights += cell.trainable_weights + return weights + + @property + def non_trainable_weights(self): + weights = [] + for cell in self._cells: + if isinstance(cell, base_layer.Layer): + weights += cell.non_trainable_weights + if not self.trainable: + trainable_weights = [] + for cell in self._cells: + if isinstance(cell, base_layer.Layer): + trainable_weights += cell.trainable_weights + return trainable_weights + weights + return weights + def call(self, inputs, state): """Run this multi-layer cell on inputs, starting from state.""" cur_state_pos = 0 -- GitLab From 80ca171714e0ea651e12135959b25873810726b7 Mon Sep 17 00:00:00 2001 From: Roy Frostig Date: Wed, 17 Oct 2018 12:37:49 -0700 Subject: [PATCH 0210/1825] Test the (general) dot operation via XRT with non-standard layouts, but disable it for now. PiperOrigin-RevId: 217568591 --- tensorflow/compiler/xrt/tests/raw_api_test.cc | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tensorflow/compiler/xrt/tests/raw_api_test.cc b/tensorflow/compiler/xrt/tests/raw_api_test.cc index ad42148ce3..84a5572dab 100644 --- a/tensorflow/compiler/xrt/tests/raw_api_test.cc +++ b/tensorflow/compiler/xrt/tests/raw_api_test.cc @@ -89,6 +89,13 @@ xla::LiteralProto FloatVector(absl::Span v) { return array.ToProto(); } +xla::LiteralProto FloatMatrix( + std::initializer_list> v, + const xla::Layout& layout) { + auto array = xla::LiteralUtil::CreateR2WithLayout(v, layout); + return array.ToProto(); +} + bool CompareLiteralProtos(const xla::LiteralProto& a, const xla::LiteralProto& b) { auto l_a = xla::Literal::CreateFromProto(a).ValueOrDie(); @@ -132,6 +139,21 @@ xla::XlaComputation AddAndScale() { return builder.Build().ValueOrDie(); } +xla::XlaComputation Dot() { + xla::XlaBuilder builder("Dot"); + auto p0 = xla::Parameter( + &builder, 0, + xla::ShapeUtil::MakeShapeWithLayout(xla::F32, {2, 2}, {0, 1}), "P0"); + auto p1 = xla::Parameter( + &builder, 1, + xla::ShapeUtil::MakeShapeWithLayout(xla::F32, {2, 1}, {0, 1}), "P1"); + xla::DotDimensionNumbers ddn; + ddn.add_lhs_contracting_dimensions(1); + ddn.add_rhs_contracting_dimensions(0); + xla::DotGeneral(p0, p1, ddn); + return builder.Build().ValueOrDie(); +} + xla::XlaComputation AddS64() { xla::XlaBuilder builder("AddS64"); auto p0 = xla::Parameter(&builder, 0, xla::ShapeUtil::MakeShape(xla::S64, {}), @@ -457,6 +479,62 @@ TEST(RawApiTest, CompileWithXlaReturnShapes) { xla_program_shape.result().layout())); } +// Disabled because of failure on TPU (b/117876141) +TEST(RawApiTest, DISABLED_DotGeneralWithLayoutTest) { + auto layout = xla::LayoutUtil::MakeLayout({0, 1}); + + xrt::XLAAllocation p0; + p0.set_device_ordinal(0); + *p0.mutable_value() = FloatMatrix({{1.0f, 2.0f}, {3.0f, 4.0f}}, layout); + xrt::XLAAllocation p1; + p1.set_device_ordinal(0); + *p1.mutable_value() = FloatMatrix({{8.0f}, {5.0f}}, layout); + + xrt::XLAComputation c; + auto config = c.mutable_config(); + auto shapes = config->mutable_program_shape(); + *shapes->add_parameters() = + xla::ShapeUtil::MakeShapeWithLayout(xla::F32, {2, 2}, {0, 1}); + *shapes->add_parameters() = + xla::ShapeUtil::MakeShapeWithLayout(xla::F32, {2, 1}, {0, 1}); + *shapes->mutable_result() = + xla::ShapeUtil::MakeShapeWithLayout(xla::F32, {2, 1}, {0, 1}); + StoreComputationSnapshot(Dot(), c.mutable_hlo_snapshot()); + + xrt::XRTExecutionConfig e; + e.set_release_input_handles(true); + e.set_release_compilation_handle(true); + + Scope root = Scope::NewRootScope().WithDevice(DeviceFromFlag()); + auto e_config = + ops::Const(root.WithDevice("/device:CPU:0"), e.SerializeAsString()); + auto computation = + ops::Const(root.WithDevice("/device:CPU:0"), c.SerializeAsString()); + auto c_handle = ops::XRTCompile(root, computation); + auto p0_value = + ops::Const(root.WithDevice("/device:CPU:0"), p0.SerializeAsString()); + auto p0_handle = ops::XRTAllocate(root, p0_value); + auto p1_value = + ops::Const(root.WithDevice("/device:CPU:0"), p1.SerializeAsString()); + auto p1_handle = ops::XRTAllocate(root, p1_value); + auto result = ops::XRTExecute(root, c_handle.handle, e_config, + {Output(p0_handle), Output(p1_handle)}); + auto read_back = ops::XRTReadLiteralAndRelease(root, result); + TF_ASSERT_OK(root.status()); + + ClientSession session(root); + std::vector outputs; + TF_EXPECT_OK(session.Run({read_back}, &outputs)); + + xla::LiteralProto response; + EXPECT_TRUE(response.ParseFromString(outputs[0].scalar()())); + + auto expected = + xla::LiteralUtil::CreateR2WithLayout({{18.0f}, {44.0f}}, layout); + + EXPECT_TRUE(CompareLiteralToLiteralProto(expected, response)); +} + TEST(RawApiTest, CompileAndExecuteZeroArg) { xrt::XLAComputation c; auto config = c.mutable_config(); -- GitLab From 10a32386b0a92fa04a9174da3e4e490472406ac3 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 12:41:11 -0700 Subject: [PATCH 0211/1825] Internal change PiperOrigin-RevId: 217569221 --- .../ISSUE_TEMPLATE/bug-performance-issue.md | 47 +++++++++---------- .../build-installation-issue.md | 40 ++++++++-------- .github/ISSUE_TEMPLATE/documentation-issue.md | 21 ++++----- .github/ISSUE_TEMPLATE/feature-request.md | 19 ++++---- .github/ISSUE_TEMPLATE/other-issues.md | 24 ++++------ 5 files changed, 72 insertions(+), 79 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-performance-issue.md b/.github/ISSUE_TEMPLATE/bug-performance-issue.md index c590a962cb..34ba4cf960 100644 --- a/.github/ISSUE_TEMPLATE/bug-performance-issue.md +++ b/.github/ISSUE_TEMPLATE/bug-performance-issue.md @@ -1,35 +1,34 @@ --------------------------------------------------------------------------------- +--- +name: Bug/Performance Issue +about: Use this template for reporting a bug or a performance issue. -name: Bug/Performance Issue about: Use this template for reporting a bug or a -performance issue. +--- --------------------------------------------------------------------------------- +Please make sure that this is a bug. As per our [GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md), we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:bug_template -Please make sure that this is a bug. As per our -[GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md) -we only address code/doc bugs, performance issues, feature requests and -build/installation issues on GitHub. tag:bug_template +**System information** +- Have I written custom code (as opposed to using a stock example script provided in TensorFlow): +- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): +- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: +- TensorFlow installed from (source or binary): +- TensorFlow version (use command below): +- Python version: +- Bazel version (if compiling from source): +- GCC/Compiler version (if compiling from source): +- CUDA/cuDNN version: +- GPU model and memory: -**System information** - Have I written custom code (as opposed to using a stock -example script provided in TensorFlow): - OS Platform and Distribution (e.g., -Linux Ubuntu 16.04): - Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if -the issue happens on mobile device: - TensorFlow installed from (source or -binary): - TensorFlow version (use command below): - Python version: - Bazel -version (if compiling from source): - GCC/Compiler version (if compiling from -source): - CUDA/cuDNN version: - GPU model and memory: -You can collect some of this information using our environment capture -[script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) -You can also obtain the TensorFlow version with python -c "import tensorflow as -tf; print(tf.GIT_VERSION, tf.VERSION)" +You can collect some of this information using our environment capture [script](https://github.com/tensorflow/tensorflow/tree/master/tools/tf_env_collect.sh) +You can also obtain the TensorFlow version with +python -c "import tensorflow as tf; print(tf.GIT_VERSION, tf.VERSION)" **Describe the current behavior** **Describe the expected behavior** -**Code to reproduce the issue** Provide a reproducible test case that is the -bare minimum necessary to generate the problem. +**Code to reproduce the issue** +Provide a reproducible test case that is the bare minimum necessary to generate the problem. -**Other info / logs** Include any logs or source code that would be helpful to -diagnose the problem. If including tracebacks, please include the full -traceback. Large logs and files should be attached. +**Other info / logs** +Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. diff --git a/.github/ISSUE_TEMPLATE/build-installation-issue.md b/.github/ISSUE_TEMPLATE/build-installation-issue.md index fac9ddfbd7..99c2fe6127 100644 --- a/.github/ISSUE_TEMPLATE/build-installation-issue.md +++ b/.github/ISSUE_TEMPLATE/build-installation-issue.md @@ -1,27 +1,29 @@ --------------------------------------------------------------------------------- +--- +name: Build/Installation Issue +about: Use this template for build/installation issues -name: Build/Installation Issue about: Use this template for build/installation -issues +--- --------------------------------------------------------------------------------- +Please make sure that this is a build/installation issue. As per our [GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md), we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:build_template + +**System information** +- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): +- Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue happens on mobile device: +- TensorFlow installed from (source or binary): +- TensorFlow version: +- Python version: +- Installed using virtualenv? pip? conda?: +- Bazel version (if compiling from source): +- GCC/Compiler version (if compiling from source): +- CUDA/cuDNN version: +- GPU model and memory: -Please make sure that this is a build/installation issue. As per our -[GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md) -we only address code/doc bugs, performance issues, feature requests and -build/installation issues on GitHub. tag:build_template -**System information** - OS Platform and Distribution (e.g., Linux Ubuntu -16.04): - Mobile device (e.g. iPhone 8, Pixel 2, Samsung Galaxy) if the issue -happens on mobile device: - TensorFlow installed from (source or binary): - -TensorFlow version: - Python version: - Installed using virtualenv? pip? -conda?: - Bazel version (if compiling from source): - GCC/Compiler version (if -compiling from source): - CUDA/cuDNN version: - GPU model and memory: **Describe the problem** -**Provide the exact sequence of commands / steps that you executed before -running into the problem** +**Provide the exact sequence of commands / steps that you executed before running into the problem** + -**Any other info / logs** Include any logs or source code that would be helpful -to diagnose the problem. If including tracebacks, please include the full -traceback. Large logs and files should be attached. +**Any other info / logs** +Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. diff --git a/.github/ISSUE_TEMPLATE/documentation-issue.md b/.github/ISSUE_TEMPLATE/documentation-issue.md index 610da5dd46..7123ca6d6c 100644 --- a/.github/ISSUE_TEMPLATE/documentation-issue.md +++ b/.github/ISSUE_TEMPLATE/documentation-issue.md @@ -1,18 +1,17 @@ --------------------------------------------------------------------------------- +--- +name: Documentation Issue +about: Use this template for documentation related issues -name: Documentation Issue about: Use this template for documentation related -issues +--- --------------------------------------------------------------------------------- +Please make sure that this is a documentation issue. As per our [GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md), we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:doc_template -Please make sure that this is a documentation issue. As per our -[GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md) -we only address code/doc bugs, performance issues, feature requests and -build/installation issues on GitHub. tag:doc_template -**System information** - TensorFlow version: - Doc Link: +**System information** +- TensorFlow version: +- Doc Link: + **Describe the documentation issue** -**We welcome contributions by users. Will you be able to update submit a PR to -fix the doc Issue?** +**We welcome contributions by users. Will you be able to update submit a PR (use the [doc style guide](https://www.tensorflow.org/community/documentation)) to fix the doc Issue?** diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 9f06e1759f..71df2e5e49 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,16 +1,17 @@ --------------------------------------------------------------------------------- +--- +name: Feature Request +about: Use this template for raising a feature request -name: Feature Request about: Use this template for raising a feature request +--- --------------------------------------------------------------------------------- +Please make sure that this is a feature request. As per our [GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md), we only address code/doc bugs, performance issues, feature requests and build/installation issues on GitHub. tag:feature_template + + +**System information** +- TensorFlow version (you are using): +- Are you willing to contribute it (Yes/No): -Please make sure that this is a feature request. As per our -[GitHub Policy](https://github.com/tensorflow/tensorflow/blob/master/ISSUES.md) -we only address code/doc bugs, performance issues, feature requests and -build/installation issues on GitHub. tag:feature_template -**System information** - TensorFlow version (you are using): - Are you willing -to contribute it (Yes/No): **Describe the feature and the current behavior/state.** diff --git a/.github/ISSUE_TEMPLATE/other-issues.md b/.github/ISSUE_TEMPLATE/other-issues.md index b53bdb3c16..2d78d9818b 100644 --- a/.github/ISSUE_TEMPLATE/other-issues.md +++ b/.github/ISSUE_TEMPLATE/other-issues.md @@ -1,21 +1,13 @@ --------------------------------------------------------------------------------- +--- +name: Other Issues +about: Use this template for any other non-support related issues -name: Other Issues about: Use this template for any other non-support related -issues +--- --------------------------------------------------------------------------------- +This template is for miscellaneous issues not covered by the other issue categories. -This template is for miscellaneous issues not covered by the other issue -categories. +For questions on how to work with TensorFlow, or support for problems that are not verified bugs in TensorFlow, please go to [StackOverflow](https://stackoverflow.com/questions/tagged/tensorflow). -For questions on how to work with TensorFlow, or support for problems that are -not verified bugs in TensorFlow, please go to -[StackOverflow](https://stackoverflow.com/questions/tagged/tensorflow). +If you are reporting a vulnerability, please use the [dedicated reporting process](https://github.com/tensorflow/tensorflow/blob/master/SECURITY.md). -If you are reporting a vulnerability, please use the -[dedicated reporting process](https://github.com/tensorflow/tensorflow/blob/master/SECURITY.md). - -For high-level discussions about TensorFlow, please post to -discuss@tensorflow.org, for questions about the development or internal workings -of TensorFlow, or if you would like to know how to contribute to TensorFlow, -please post to developers@tensorflow.org. +For high-level discussions about TensorFlow, please post to discuss@tensorflow.org, for questions about the development or internal workings of TensorFlow, or if you would like to know how to contribute to TensorFlow, please post to developers@tensorflow.org. -- GitLab From d1a23c501f95a91f74282a481ddd29c64cf6da56 Mon Sep 17 00:00:00 2001 From: Russell Power Date: Wed, 17 Oct 2018 12:51:05 -0700 Subject: [PATCH 0212/1825] Fix triggering of asynchronous checkpoints. PiperOrigin-RevId: 217570792 --- .../contrib/tpu/python/tpu/async_checkpoint.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/async_checkpoint.py b/tensorflow/contrib/tpu/python/tpu/async_checkpoint.py index 20b7ba0997..700598d2f4 100644 --- a/tensorflow/contrib/tpu/python/tpu/async_checkpoint.py +++ b/tensorflow/contrib/tpu/python/tpu/async_checkpoint.py @@ -114,15 +114,12 @@ class AsyncCheckpointSaverHook(basic_session_run_hooks.CheckpointSaverHook): return SessionRunArgs(self._global_step_tensor) def after_run(self, run_context, run_values): - stale_global_step = run_values.results - if self._timer.should_trigger_for_step(stale_global_step + - self._steps_per_run): - # get the real value after train op. - global_step = run_context.session.run(self._global_step_tensor) - if self._timer.should_trigger_for_step(global_step): - self._timer.update_last_triggered_step(global_step) - if self._save(run_context.session, global_step): - run_context.request_stop() + global_step = run_context.session.run(self._global_step_tensor) + if self._timer.should_trigger_for_step(global_step): + self._timer.update_last_triggered_step(global_step) + logging.info("Triggering checkpoint. %s", global_step) + if self._save(run_context.session, global_step): + run_context.request_stop() def end(self, session): if self._save_thread: -- GitLab From d48968cc90d3c466ddaee8210cf05d094f0854bd Mon Sep 17 00:00:00 2001 From: Mark Heffernan Date: Wed, 17 Oct 2018 12:54:31 -0700 Subject: [PATCH 0213/1825] Add HloModuleGroup constructor which takes an rvalue vector of modules. PiperOrigin-RevId: 217571343 --- .../compiler/xla/service/hlo_module_group.cc | 14 ++++++++++++++ tensorflow/compiler/xla/service/hlo_module_group.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/tensorflow/compiler/xla/service/hlo_module_group.cc b/tensorflow/compiler/xla/service/hlo_module_group.cc index 8999ac9f32..69d57c3f14 100644 --- a/tensorflow/compiler/xla/service/hlo_module_group.cc +++ b/tensorflow/compiler/xla/service/hlo_module_group.cc @@ -30,6 +30,14 @@ HloModuleGroup::HloModuleGroup(absl::string_view name, } } +HloModuleGroup::HloModuleGroup( + absl::string_view name, std::vector>&& modules) + : name_(name) { + for (auto& module : modules) { + push_back(std::move(module)); + } +} + std::vector> HloModuleGroup::ConsumeModules() { std::vector> ret_modules = std::move(modules_); @@ -82,6 +90,12 @@ void HloModuleGroup::push_back(std::unique_ptr module) { module_ptrs_.push_back(modules_.back().get()); } +void HloModuleGroup::ReplaceModule(int index, + std::unique_ptr module) { + modules_.at(index) = std::move(module); + module_ptrs_.at(index) = modules_.at(index).get(); +} + std::ostream& operator<<(std::ostream& out, const HloModuleGroup& group) { out << group.ToString(); return out; diff --git a/tensorflow/compiler/xla/service/hlo_module_group.h b/tensorflow/compiler/xla/service/hlo_module_group.h index 7c39cf1781..c4b10f3b22 100644 --- a/tensorflow/compiler/xla/service/hlo_module_group.h +++ b/tensorflow/compiler/xla/service/hlo_module_group.h @@ -40,6 +40,8 @@ class HloModuleGroup { // Construct a module group containing any number of modules. HloModuleGroup(absl::string_view name, absl::Span> modules); + HloModuleGroup(absl::string_view name, + std::vector>&& modules); // Returns the modules contained in the group. const std::vector& modules() const { return module_ptrs_; } -- GitLab From b021a8b041a159dcfe6669b43bf6357c69dff688 Mon Sep 17 00:00:00 2001 From: Tong Shen Date: Wed, 17 Oct 2018 12:54:57 -0700 Subject: [PATCH 0214/1825] Add function to preprocess TF graph before encapsulating XLA computations. PiperOrigin-RevId: 217571411 --- tensorflow/compiler/jit/BUILD | 4 + tensorflow/compiler/jit/encapsulate_util.cc | 325 ++++++++++++++++++ tensorflow/compiler/jit/encapsulate_util.h | 71 ++++ .../compiler/jit/encapsulate_util_test.cc | 175 +++++++++- tensorflow/compiler/tf2xla/tf2xla_util.cc | 57 ++- tensorflow/compiler/tf2xla/tf2xla_util.h | 15 + 6 files changed, 644 insertions(+), 3 deletions(-) diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD index 311313b8f2..9dfa0fa8c5 100644 --- a/tensorflow/compiler/jit/BUILD +++ b/tensorflow/compiler/jit/BUILD @@ -411,7 +411,11 @@ cc_library( hdrs = ["encapsulate_util.h"], deps = [ ":shape_inference", + "//tensorflow/compiler/tf2xla:tf2xla_util", + "//tensorflow/core:framework", "//tensorflow/core:graph", + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", "@com_google_absl//absl/strings", "@com_google_absl//absl/types:optional", ], diff --git a/tensorflow/compiler/jit/encapsulate_util.cc b/tensorflow/compiler/jit/encapsulate_util.cc index 870a265f29..a3581b4fa0 100644 --- a/tensorflow/compiler/jit/encapsulate_util.cc +++ b/tensorflow/compiler/jit/encapsulate_util.cc @@ -20,6 +20,10 @@ limitations under the License. #include "absl/strings/str_cat.h" #include "absl/types/optional.h" #include "tensorflow/compiler/jit/shape_inference.h" +#include "tensorflow/compiler/tf2xla/tf2xla_util.h" +#include "tensorflow/core/framework/node_def_util.h" +#include "tensorflow/core/graph/node_builder.h" +#include "tensorflow/core/lib/core/error_codes.pb.h" namespace tensorflow { @@ -36,10 +40,319 @@ absl::optional GetStringAttr(const Node& n, const string& attr_name) { } } +// Adds a value to the node's list attribute. +template +Status AppendToListAttr(Node* n, const string& attr_name, const string& value) { + std::vector attr_value; + Status s = GetNodeAttr(n->attrs(), attr_name, &attr_value); + if (!s.ok() && s.code() != error::NOT_FOUND) { + return s; + } + + n->ClearAttr(attr_name); + attr_value.push_back(value); + n->AddAttr(attr_name, attr_value); + return Status::OK(); +} + +// Replaces attribute value. +template +void ReplaceAttr(Node* n, const string& attr_name, const T& value) { + n->ClearAttr(attr_name); + n->AddAttr(attr_name, value); +} + +// Step 1a ~ 1d for PreprocessForEncapsulation(). See comments of +// PreprocessForEncapsulation() for details. +Status ProcessControlEdges(Graph* g, const string& xla_computation_attr_name, + const string& outside_compilation_attr_name) { + // Gather edges to remove. We should not remove the edge while iterating. + std::vector edges_to_remove; + for (const Edge* e : g->edges()) { + if (!e->IsControlEdge()) { + continue; + } + + auto src_xla_computation = + GetStringAttr(*e->src(), xla_computation_attr_name); + auto dst_xla_computation = + GetStringAttr(*e->dst(), xla_computation_attr_name); + auto src_outside_compilation = + GetStringAttr(*e->src(), outside_compilation_attr_name); + auto dst_outside_compilation = + GetStringAttr(*e->dst(), outside_compilation_attr_name); + + if (!src_xla_computation && !dst_xla_computation) { + continue; + } else if (src_xla_computation && !dst_xla_computation) { + if (src_outside_compilation) { + // Case 1d: outside compilation to host computation control edge. + TF_RETURN_IF_ERROR(AppendToListAttr( + e->dst(), kXlaControlDependenciesAttrName, e->src()->name())); + } + } else if (!src_xla_computation && dst_xla_computation) { + if (dst_outside_compilation) { + // Case 1d: host computation control to outside compilation edge. + TF_RETURN_IF_ERROR(AppendToListAttr( + e->dst(), kXlaControlDependenciesAttrName, e->src()->name())); + } + } else { // src_xla_computation && dst_xla_computation + if (*src_xla_computation != *dst_xla_computation) { + if (src_outside_compilation && dst_outside_compilation) { + // Case 1c: outside compilation to outside compilation control edge. + edges_to_remove.push_back(e); + + TF_RETURN_IF_ERROR(AppendToListAttr( + e->dst(), kXlaControlDependenciesAttrName, e->src()->name())); + } else if (src_outside_compilation && !dst_outside_compilation) { + // Case 1b: outside compilation to another XLA computaition control + // edge. + TF_RETURN_IF_ERROR(AppendToListAttr( + e->src(), kXlaConnectedToOtherXlaComputationAttrName, + *dst_xla_computation)); + } else if (!src_outside_compilation && dst_outside_compilation) { + // Case 1b: another XLA computaition to outside compilation control + // edge. + TF_RETURN_IF_ERROR(AppendToListAttr( + e->dst(), kXlaConnectedFromOtherXlaComputationAttrName, + *src_xla_computation)); + } + } else { // *src_xla_computation == *dst_xla_computation + if (src_outside_compilation && dst_outside_compilation) { + if (*src_outside_compilation != *dst_outside_compilation) { + // Case 1c: outside compilation to outside compilation control edge. + edges_to_remove.push_back(e); + + TF_RETURN_IF_ERROR(AppendToListAttr( + e->dst(), kXlaControlDependenciesAttrName, e->src()->name())); + } + } else if (src_outside_compilation && !dst_outside_compilation) { + // Case 1a: outside compilation to its XLA computation control edge. + ReplaceAttr(e->src(), kXlaConnectedToXlaComputationAttrName, true); + } else if (!src_outside_compilation && dst_outside_compilation) { + // Case 1a: XLA computation to outside compilation in it control edge. + ReplaceAttr(e->dst(), kXlaConnectedFromXlaComputationAttrName, true); + } + } + } + } + + for (auto e : edges_to_remove) { + g->RemoveEdge(e); + } + return Status::OK(); +} + +// Step 2 for PreprocessForEncapsulation(). See comments of +// PreprocessForEncapsulation() for details. +Status ProcessXlaToXlaDataEdges(Graph* g, + const string& xla_computation_attr_name, + const string& outside_compilation_attr_name) { + // Gather edges between XLA computations. Notice that we do not store `Edge*` + // directly because we remove some nodes while adding Identity nodes, and + // those Edge pointers might be invalidated. + struct EdgeInfo { + int dst_input, dst_node_id; + }; + std::vector edges; + for (const Edge* e : g->edges()) { + if (e->IsControlEdge()) { + continue; + } + + auto src_xla_computation = + GetStringAttr(*e->src(), xla_computation_attr_name); + auto dst_xla_computation = + GetStringAttr(*e->dst(), xla_computation_attr_name); + auto src_outside_compilation = + GetStringAttr(*e->src(), outside_compilation_attr_name); + auto dst_outside_compilation = + GetStringAttr(*e->dst(), outside_compilation_attr_name); + if (!src_xla_computation || !dst_xla_computation) { + continue; + } + + if (*src_xla_computation != *dst_xla_computation) { + if (src_outside_compilation || dst_outside_compilation) { + edges.push_back(EdgeInfo{e->dst_input(), e->dst()->id()}); + VLOG(4) << "XLA -> XLA edge: " << e->DebugString(); + } + } else { // *src_xla_computation == *dst_xla_computation + if (src_outside_compilation && dst_outside_compilation && + *src_outside_compilation != *dst_outside_compilation) { + edges.push_back(EdgeInfo{e->dst_input(), e->dst()->id()}); + VLOG(4) << "XLA -> XLA edge: " << e->DebugString(); + } + } + } + + // For each XLA -> XLA edge, add an Identity node between src and dst. + for (int i = 0; i < edges.size(); i++) { + Node* dst = g->FindNodeId(edges[i].dst_node_id); + const Edge* e; + TF_RETURN_IF_ERROR(dst->input_edge(edges[i].dst_input, &e)); + Node* src = e->src(); + int src_output = e->src_output(), dst_input = e->dst_input(); + g->RemoveEdge(e); + + // Create Identity node, and connect it between `src` and `dst`. + string identity_node_name = + absl::StrCat("bridge_", src->name(), "_", dst->name()); + DataType dtype = src->output_type(src_output); + TF_ASSIGN_OR_RETURN(Node * identity_node, + BuildIdentityNode(g, identity_node_name, dtype, src, + /*requested_device=*/absl::nullopt)); + identity_node->AddAttr(kBridgeSourceNodeAttrName, src->name()); + g->AddEdge(src, src_output, identity_node, 0); + g->AddEdge(identity_node, 0, dst, dst_input); + + // Replace `e->dst()` because its input node changed. + NodeDef new_def = dst->def(); + *new_def.mutable_input(dst_input) = identity_node->name(); + TF_ASSIGN_OR_RETURN(Node * dst_replace_node, ReplaceNode(g, dst, new_def)); + + // Other edge in `edges` might have `e->dst()` as src or dst + // node. Before removing `e->dst()`, replace those edges with corresponding + // edges for `dst_replace_node`. + for (int j = i + 1; j < edges.size(); j++) { + if (edges[j].dst_node_id == edges[i].dst_node_id) { + edges[j].dst_node_id = dst_replace_node->id(); + } + } + } + return Status::OK(); +} + +// Step 3 for PreprocessForEncapsulation(). See comments of +// PreprocessForEncapsulation() for details. +Status ProcessDataEdgeBetweenOutsideCompilationAndHostComputation( + Graph* g, const string& xla_computation_attr_name, + const string& outside_compilation_attr_name) { + // Gather edges between outside compilation and host computation. Notice that + // we do not store `Edge*` directly because we remove some nodes while adding + // Identity nodes, and those Edge pointers might be invalidated. + struct EdgeInfo { + int dst_input, dst_node_id; + bool is_host_to_outside_compilation; + }; + std::vector edges; + for (const Edge* e : g->edges()) { + if (e->IsControlEdge()) { + continue; + } + + if (e->src()->attrs().Find(xla_computation_attr_name) == nullptr && + e->dst()->attrs().Find(xla_computation_attr_name) != nullptr && + e->dst()->attrs().Find(outside_compilation_attr_name) != nullptr) { + edges.push_back(EdgeInfo{e->dst_input(), e->dst()->id(), + /*is_host_to_outside_compilation=*/true}); + VLOG(4) << "Host -> oc edge: " << e->DebugString(); + } else if (e->dst()->attrs().Find(xla_computation_attr_name) == nullptr && + e->src()->attrs().Find(xla_computation_attr_name) != nullptr && + e->src()->attrs().Find(outside_compilation_attr_name) != + nullptr) { + edges.push_back(EdgeInfo{e->dst_input(), e->dst()->id(), + /*is_host_to_outside_compilation=*/false}); + VLOG(4) << "Oc -> host edge: " << e->DebugString(); + } + } + + // Remove the edge from host to outside compilation. Add a placeholder as + // outside compilation node input. + std::map placeholders; + for (int i = 0; i < edges.size(); i++) { + Node* dst = g->FindNodeId(edges[i].dst_node_id); + const Edge* e; + TF_RETURN_IF_ERROR(dst->input_edge(edges[i].dst_input, &e)); + Node* src = e->src(); + int src_output = e->src_output(), dst_input = e->dst_input(); + g->RemoveEdge(e); + + // Find or create placeholder node. + string new_name = + edges[i].is_host_to_outside_compilation + ? absl::StrCat(src->name(), "_host_to_oc_placeholder") + : absl::StrCat(src->name(), "_oc_to_host_placeholder"); + auto iter = placeholders.find(new_name); + Node* placeholder_node; + if (iter == placeholders.end()) { + NodeDefBuilder placeholder_builder(new_name, "Placeholder"); + placeholder_builder.Attr("dtype", src->output_type(src_output)); + if (edges[i].is_host_to_outside_compilation) { + placeholder_builder.Attr(kHostToOutsideCompilationOriginalNodeAttrName, + src->name()); + placeholder_builder.Attr(kHostToOutsideCompilationSrcOutputAttrName, + src_output); + // If this placeholder node is in outside compilation, we need to set + // `xla_computation_attr_name` and `outside_compilation_attr_name`. + string xla_computation_attr, outside_compilation_attr; + TF_RETURN_IF_ERROR(GetNodeAttr(dst->attrs(), xla_computation_attr_name, + &xla_computation_attr)); + TF_RETURN_IF_ERROR(GetNodeAttr(dst->attrs(), + outside_compilation_attr_name, + &outside_compilation_attr)); + placeholder_builder.Attr(xla_computation_attr_name, + xla_computation_attr); + placeholder_builder.Attr(outside_compilation_attr_name, + outside_compilation_attr); + } else { + placeholder_builder.Attr(kOutsideCompilationToHostOriginalNodeAttrName, + src->name()); + placeholder_builder.Attr(kOutsideCompilationToHostSrcOutputAttrName, + src_output); + } + NodeDef placeholder_def; + TF_RETURN_IF_ERROR(placeholder_builder.Finalize(&placeholder_def)); + Status s; + placeholder_node = g->AddNode(placeholder_def, &s); + TF_RETURN_IF_ERROR(s); + placeholders[new_name] = placeholder_node; + } else { + placeholder_node = iter->second; + } + g->AddEdge(placeholder_node, 0, dst, dst_input); + g->RemoveEdge(e); + + // Replace `e->dst()` because its input node changed. + NodeDef new_def = dst->def(); + *new_def.mutable_input(dst_input) = placeholder_node->name(); + TF_ASSIGN_OR_RETURN(Node * dst_replace_node, ReplaceNode(g, dst, new_def)); + + // Other edge in `edges` might have `e->dst()` as src or dst + // node. Before removing `e->dst()`, replace those edges with corresponding + // edges for `dst_replace_node`. + for (int j = i + 1; j < edges.size(); j++) { + if (edges[j].dst_node_id == edges[i].dst_node_id) { + edges[j].dst_node_id = dst_replace_node->id(); + } + } + } + return Status::OK(); +} + } // namespace const char kXlaInferredShapesAttrName[] = "_xla_inferred_shapes"; +const char kXlaConnectedToXlaComputationAttrName[] = + "_xla_connected_to_xla_computation"; +const char kXlaConnectedFromXlaComputationAttrName[] = + "_xla_connected_from_xla_computation"; +const char kXlaConnectedToOtherXlaComputationAttrName[] = + "_xla_connected_to_other_xla_computation"; +const char kXlaConnectedFromOtherXlaComputationAttrName[] = + "_xla_connected_from_other_xla_computation"; +const char kXlaControlDependenciesAttrName[] = "_xla_control_dependencies"; +const char kBridgeSourceNodeAttrName[] = "_xla_bridge_src"; +const char kOutsideCompilationToHostOriginalNodeAttrName[] = + "_xla_oc_to_host_node_name"; +const char kOutsideCompilationToHostSrcOutputAttrName[] = + "_xla_oc_to_host_src_output"; +const char kHostToOutsideCompilationOriginalNodeAttrName[] = + "_xla_host_to_oc_node_name"; +const char kHostToOutsideCompilationSrcOutputAttrName[] = + "_xla_host_to_oc_src_output"; + Status PerformStaticShapeInferenceBeforeEncapsulation( Graph* g, const string& xla_computation_attr_name, const string& outside_compilation_attr_name) { @@ -91,4 +404,16 @@ Status PerformStaticShapeInferenceBeforeEncapsulation( return Status::OK(); } +Status PreprocessForEncapsulation(Graph* g, + const string& xla_computation_attr_name, + const string& outside_compilation_attr_name) { + TF_RETURN_IF_ERROR(ProcessControlEdges(g, xla_computation_attr_name, + outside_compilation_attr_name)); + TF_RETURN_IF_ERROR(ProcessXlaToXlaDataEdges(g, xla_computation_attr_name, + outside_compilation_attr_name)); + TF_RETURN_IF_ERROR(ProcessDataEdgeBetweenOutsideCompilationAndHostComputation( + g, xla_computation_attr_name, outside_compilation_attr_name)); + return Status::OK(); +} + } // namespace tensorflow diff --git a/tensorflow/compiler/jit/encapsulate_util.h b/tensorflow/compiler/jit/encapsulate_util.h index bc46521b98..bd76c844c4 100644 --- a/tensorflow/compiler/jit/encapsulate_util.h +++ b/tensorflow/compiler/jit/encapsulate_util.h @@ -44,6 +44,77 @@ Status PerformStaticShapeInferenceBeforeEncapsulation( Graph* g, const string& xla_computation_attr_name, const string& outside_compilation_attr_name); +// Attribute indicating that some ops in this node's XLA computation has control +// dependency on this node. Attribute value will always be "true". +extern const char kXlaConnectedToXlaComputationAttrName[]; + +// Attribute indicating that this node has control dependency on some ops in +// this node's XLA computation. Attribute value will always be "true". +extern const char kXlaConnectedFromXlaComputationAttrName[]; + +// Attribute indicating that some ops in other XLA computation has control +// dependency on this node. Attribute value will be a list of string (XLA +// computation names). +extern const char kXlaConnectedToOtherXlaComputationAttrName[]; + +// Attribute indicating that this node has control dependency on some ops in +// other XLA computation. Attribute value will be a list of string (XLA +// computation names). +extern const char kXlaConnectedFromOtherXlaComputationAttrName[]; + +// Attribute indicating that this node has control dependencies on some other +// nodes. Attribute value will be a list of string (node names). +extern const char kXlaControlDependenciesAttrName[]; + +// Attribute indicating that this is an Identity node added to act as a bridge +// between different XLA computations. Attribute value will be string (source +// node name). +extern const char kBridgeSourceNodeAttrName[]; + +// Attribute indicating that this is an Placeholder node added to act as a +// temporary input node for an outside compilation node. Attribute value will be +// string (original input node name). +extern const char kOutsideCompilationToHostOriginalNodeAttrName[]; + +// Attribute indicating that this is an Placeholder node added to act as a +// temporary input node for an outside compilation node. Attribute value will be +// int (src_output for original edge). +extern const char kOutsideCompilationToHostSrcOutputAttrName[]; + +// Attribute indicating that this is an Placeholder node added to act as a +// temporary input node for an host node. Attribute value will be string +// (original input node name). +extern const char kHostToOutsideCompilationOriginalNodeAttrName[]; + +// Attribute indicating that this is an Placeholder node added to act as a +// temporary input node for a host node. Attribute value will be int (src_output +// for original edge). +extern const char kHostToOutsideCompilationSrcOutputAttrName[]; + +// Preprocesses the graph for encapsulation. It will perform the following +// operations in order: +// +// 1a. For control edges between outside compilation and its XLA computation, +// add attr "kXlaConnected{From, To}XlaComputationAttrName = true" to the +// outside compilation node. +// 1b. For control edges between outside compilation and another XLA +// computation, add attr "kXlaConnected{From, To}OtherXlaComputationAttrName +// = XLA computation node name" to the outside compilation node. +// 1c. For control edges between different outside compilations, remove the edge +// and add attr "kXlaControlDependenciesAttrName = src node name" to dst +// node. +// 1d. For control edges between outside compilation and host computation, +// remove the edge and add attr "kXlaControlDependenciesAttrName = src node +// name" to dst node. +// 2. For data edges between different XLA computations, if either src or dst +// is outside compilation, add an Identity node in between the edge. The +// identity node will have attr kBridgeSourceNodeAttrName. +// 3. For data edges between outside compilation and host computation, remove +// the edge and create a Placeholder node as dst node's input. +Status PreprocessForEncapsulation(Graph* g, + const string& xla_computation_attr_name, + const string& outside_compilation_attr_name); + } // namespace tensorflow #endif // TENSORFLOW_COMPILER_JIT_ENCAPSULATE_UTIL_H_ diff --git a/tensorflow/compiler/jit/encapsulate_util_test.cc b/tensorflow/compiler/jit/encapsulate_util_test.cc index 53bdf55ab2..18422f73b5 100644 --- a/tensorflow/compiler/jit/encapsulate_util_test.cc +++ b/tensorflow/compiler/jit/encapsulate_util_test.cc @@ -47,8 +47,8 @@ TEST(PerformStaticShapeInferenceBeforeEncapsulationTest, Basic) { PerformStaticShapeInferenceBeforeEncapsulation(&g, "_xla", "_oc")); // Check that only "add" node now has _xla_inferred_shapes attr. - std::vector nodes_with_inferred_shape; - for (Node* n : g.nodes()) { + std::vector nodes_with_inferred_shape; + for (Node *n : g.nodes()) { if (HasNodeAttr(n->def(), kXlaInferredShapesAttrName)) { nodes_with_inferred_shape.push_back(n); } @@ -65,4 +65,175 @@ TEST(PerformStaticShapeInferenceBeforeEncapsulationTest, Basic) { EXPECT_EQ(shape_proto.dim(0).size(), 2); } +TEST(PreprocessForEncapsulationTest, ControlEdges) { + // Build the graph: + // "const_0" and "const_1" in host computation + // "add" = "const_0" + "const_1" in XLA computation 0 + // "identity0" = "add" in XLA computation 0 & outside compilation 0 + // "identity1" = "identity0" in XLA computation 0 + // "identity2" = "identity1" in host computation + // "identity3" = "identity2" in XLA computation 1 + // "identity4" = "identity3" in XLA computation 1 & outside compilation 1 + // "identity5" = "identity4" in XLA computation 1 + // "identity6" = "identity5" in host computation + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output const_0 = ops::Const(s.WithOpName("const_0"), 1, {}); + Output const_1 = ops::Const(s.WithOpName("const_1"), 2, {}); + Output add = ops::Add(s.WithOpName("add"), const_0, const_1); + Output identity0 = ops::Identity(s.WithOpName("identity0"), add); + Output identity1 = ops::Identity(s.WithOpName("identity1"), identity0); + Output identity2 = ops::Identity(s.WithOpName("identity2"), identity1); + Output identity3 = ops::Identity(s.WithOpName("identity3"), identity2); + Output identity4 = ops::Identity(s.WithOpName("identity4"), identity3); + Output identity5 = ops::Identity(s.WithOpName("identity5"), identity4); + Graph g(OpRegistry::Global()); + TF_CHECK_OK(s.ToGraph(&g)); + auto node_index = g.BuildNodeNameIndex(); + + // Set XLA computation/outside compilation attr, and add control edges. + Node *const0_node = node_index["const_0"], *add_node = node_index["add"], + *identity0_node = node_index["identity0"], + *identity1_node = node_index["identity1"], + *identity2_node = node_index["identity2"], + *identity3_node = node_index["identity3"], + *identity4_node = node_index["identity4"], + *identity5_node = node_index["identity5"]; + add_node->AddAttr("_xla", "0"); + identity0_node->AddAttr("_xla", "0"); + identity0_node->AddAttr("_oc", "0"); + identity1_node->AddAttr("_xla", "0"); + identity3_node->AddAttr("_xla", "1"); + identity4_node->AddAttr("_xla", "1"); + identity4_node->AddAttr("_oc", "0"); + identity5_node->AddAttr("_xla", "1"); + // Case 1a: control edges between outside compilation and its XLA computation. + g.AddControlEdge(add_node, identity0_node); + g.AddControlEdge(identity0_node, identity1_node); + // Case 1b: control edges between outside compilation and another XLA + // computation. + g.AddControlEdge(identity0_node, identity3_node); + g.AddControlEdge(identity1_node, identity4_node); + // Case 1c: control edges between different outside compilations. + g.AddControlEdge(identity0_node, identity4_node); + // Case 1d: control edges between outside compilation and host computation. + g.AddControlEdge(const0_node, identity0_node); + g.AddControlEdge(identity0_node, identity2_node); + + TF_CHECK_OK(PreprocessForEncapsulation(&g, "_xla", "_oc")); + + // Case 1a: add attr "_xla_connected_{from/to}_xla_computation = true" to the + // outside compilation node. + EXPECT_TRUE(HasNodeAttr(identity0_node->def(), + kXlaConnectedFromXlaComputationAttrName)); + EXPECT_TRUE(HasNodeAttr(identity0_node->def(), + kXlaConnectedToXlaComputationAttrName)); + // Case 1b: add attr "_xla_control_deps_{from/to} = XLA computation node name" + // to the outside compilation node. + std::vector attr; + TF_CHECK_OK(GetNodeAttr(identity0_node->def(), + kXlaConnectedToOtherXlaComputationAttrName, &attr)); + EXPECT_EQ(attr.size(), 1); + EXPECT_EQ(attr[0], "1"); + attr.clear(); + TF_CHECK_OK(GetNodeAttr(identity4_node->def(), + kXlaConnectedFromOtherXlaComputationAttrName, &attr)); + EXPECT_EQ(attr.size(), 1); + EXPECT_EQ(attr[0], "0"); + // Case 1c: add attr "_xla_control_deps = src node name" to dst node. + attr.clear(); + TF_CHECK_OK(GetNodeAttr(identity4_node->def(), + kXlaControlDependenciesAttrName, &attr)); + EXPECT_EQ(attr.size(), 1); + EXPECT_EQ(attr[0], "identity0"); + // Case 1d: add attr "_xla_control_deps = src node name" to dst node. + attr.clear(); + TF_CHECK_OK(GetNodeAttr(identity0_node->def(), + kXlaControlDependenciesAttrName, &attr)); + EXPECT_EQ(attr.size(), 1); + EXPECT_EQ(attr[0], "const_0"); + attr.clear(); + TF_CHECK_OK(GetNodeAttr(identity2_node->def(), + kXlaControlDependenciesAttrName, &attr)); + EXPECT_EQ(attr.size(), 1); + EXPECT_EQ(attr[0], "identity0"); +} + +TEST(PreprocessForEncapsulationTest, DataEdges) { + // Build the graph: + // "const_0" and "const_1" in host computation + // "add0" = "const_0" + "const_1" in XLA computation 0 + // "add1" = "add0" + "const_0" in XLA computation 0 & outside compilation 0 + // "identity0" = "add1" in XLA computation 0 + // "add2" = "add1" + "identity0" in host computation + // "add3" = "add1" + "add2" in XLA computation 1 + // "add4" = "identity0" + "add2" in XLA computation 1 & outside compilation 1 + // "identity1" = "add4" in XLA computation 1 + // "identity2" = "identity1" in host computation + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output const_0 = ops::Const(s.WithOpName("const_0"), 1, {}); + Output const_1 = ops::Const(s.WithOpName("const_1"), 2, {}); + Output add0 = ops::Add(s.WithOpName("add0"), const_0, const_1); + Output add1 = ops::Add(s.WithOpName("add1"), add0, const_0); + Output identity0 = ops::Identity(s.WithOpName("identity0"), add1); + Output add2 = ops::Add(s.WithOpName("add2"), add1, identity0); + Output add3 = ops::Add(s.WithOpName("add3"), add1, add2); + Output add4 = ops::Add(s.WithOpName("add4"), identity0, add2); + Output identity1 = ops::Identity(s.WithOpName("identity1"), add4); + Output identity2 = ops::Identity(s.WithOpName("identity2"), add4); + Graph g(OpRegistry::Global()); + TF_CHECK_OK(s.ToGraph(&g)); + auto node_index = g.BuildNodeNameIndex(); + + // Set XLA computation/outside compilation attr. + Node *add0_node = node_index["add0"], *add1_node = node_index["add1"], + *identity0_node = node_index["identity0"], + *add3_node = node_index["add3"], *add4_node = node_index["add4"], + *identity1_node = node_index["identity1"]; + add0_node->AddAttr("_xla", "0"); + add1_node->AddAttr("_xla", "0"); + add1_node->AddAttr("_oc", "0"); + identity0_node->AddAttr("_xla", "0"); + add3_node->AddAttr("_xla", "1"); + add4_node->AddAttr("_xla", "1"); + add4_node->AddAttr("_oc", "0"); + identity1_node->AddAttr("_xla", "1"); + + TF_CHECK_OK(PreprocessForEncapsulation(&g, "_xla", "_oc")); + + // Check input nodes for related data edges. + node_index = g.BuildNodeNameIndex(); + // Step 2: add an Identity node between different XLA computations. + Node *bridge_add1_add3 = node_index["bridge_add1_add3"]; + EXPECT_NE(bridge_add1_add3, nullptr); + string str; + TF_CHECK_OK( + GetNodeAttr(bridge_add1_add3->attrs(), kBridgeSourceNodeAttrName, &str)); + EXPECT_EQ(str, "add1"); + Node *bridge_identity0_add4 = node_index["bridge_identity0_add4"]; + EXPECT_NE(bridge_identity0_add4, nullptr); + // Step 3: add placeholder for edges between host computation and outside + // compilation. + EXPECT_EQ(bridge_add1_add3->def().input(0), "add1_oc_to_host_placeholder"); + Node *add1_oc_to_host_placeholder = node_index["add1_oc_to_host_placeholder"]; + TF_CHECK_OK(GetNodeAttr(add1_oc_to_host_placeholder->attrs(), + kOutsideCompilationToHostOriginalNodeAttrName, &str)); + EXPECT_EQ(str, "add1"); + int i; + TF_CHECK_OK(GetNodeAttr(add1_oc_to_host_placeholder->attrs(), + kOutsideCompilationToHostSrcOutputAttrName, &i)); + EXPECT_EQ(i, 0); + add4_node = node_index["add4"]; + ASSERT_NE(add4_node, nullptr); + EXPECT_EQ(add4_node->def().input(0), + "bridge_identity0_add4_host_to_oc_placeholder"); + Node *identity0_host_to_oc_placeholder = + node_index["bridge_identity0_add4_host_to_oc_placeholder"]; + TF_CHECK_OK(GetNodeAttr(identity0_host_to_oc_placeholder->attrs(), + kHostToOutsideCompilationOriginalNodeAttrName, &str)); + EXPECT_EQ(str, "bridge_identity0_add4"); + TF_CHECK_OK(GetNodeAttr(identity0_host_to_oc_placeholder->attrs(), + kHostToOutsideCompilationSrcOutputAttrName, &i)); + EXPECT_EQ(i, 0); +} + } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/tf2xla_util.cc b/tensorflow/compiler/tf2xla/tf2xla_util.cc index cc83db0562..34a0f81c0a 100644 --- a/tensorflow/compiler/tf2xla/tf2xla_util.cc +++ b/tensorflow/compiler/tf2xla/tf2xla_util.cc @@ -21,7 +21,6 @@ limitations under the License. #include #include "absl/strings/str_cat.h" -#include "absl/types/optional.h" #include "tensorflow/compiler/tf2xla/sharding_util.h" #include "tensorflow/compiler/tf2xla/tf2xla.pb.h" #include "tensorflow/compiler/xla/xla_data.pb.h" @@ -465,4 +464,60 @@ Status CachedFunctionHandles::ReleaseAllHandles() { return result; } +xla::StatusOr ReplaceNode(Graph* g, Node* n, const NodeDef& node_def) { + // Create the replacement node. + Status s; + Node* new_node = g->AddNode(node_def, &s); + if (!s.ok()) { + return s; + } + + // Record original node's output edges and remove them first. This is to avoid + // multiple producers for dst nodes' input. + std::vector out_edge_info; + std::vector out_edges; + for (const Edge* edge : n->out_edges()) { + out_edges.push_back(edge); + out_edge_info.push_back( + {edge->dst(), edge->src_output(), edge->dst_input()}); + } + for (const Edge* edge : out_edges) { + g->RemoveEdge(edge); + } + + // Add original node's input and output edges to the replacement node. + for (const Edge* in_edge : n->in_edges()) { + g->AddEdge(in_edge->src(), in_edge->src_output(), new_node, + in_edge->dst_input()); + } + for (const OutEdgeInfo& out_edge : out_edge_info) { + g->AddEdge(new_node, out_edge.src_output, out_edge.dst, out_edge.dst_input); + } + + // Remove the original node. + g->RemoveNode(n); + + return new_node; +} + +xla::StatusOr BuildIdentityNode( + Graph* graph, const string& node_name, DataType dtype, const Node* input, + absl::optional requested_device) { + // Create identity node. + NodeDef ndef; + ndef.set_name(node_name); + ndef.set_op("Identity"); + if (input) { + ndef.add_input(input->name()); + } + if (requested_device) { + ndef.set_device(*requested_device); + } + AddNodeAttr("T", dtype, &ndef); + Status s; + Node* id_node = graph->AddNode(ndef, &s); + TF_RETURN_IF_ERROR(s); + return id_node; +} + } // namespace tensorflow diff --git a/tensorflow/compiler/tf2xla/tf2xla_util.h b/tensorflow/compiler/tf2xla/tf2xla_util.h index b974b99822..95cf589f0b 100644 --- a/tensorflow/compiler/tf2xla/tf2xla_util.h +++ b/tensorflow/compiler/tf2xla/tf2xla_util.h @@ -18,6 +18,7 @@ limitations under the License. #include +#include "absl/types/optional.h" #include "tensorflow/compiler/tf2xla/tf2xla.pb.h" #include "tensorflow/compiler/xla/status_macros.h" #include "tensorflow/core/framework/graph.pb.h" @@ -168,6 +169,20 @@ class CachedFunctionHandles { TF_DISALLOW_COPY_AND_ASSIGN(CachedFunctionHandles); }; +// Struct for node's output edge info. +struct OutEdgeInfo { + Node* dst; + int src_output, dst_input; +}; + +// Replaces node `n` with a new node whose NodeDef is `node_def`. +xla::StatusOr ReplaceNode(Graph* g, Node* n, const NodeDef& node_def); + +// Helper function that builds an Identity node. +xla::StatusOr BuildIdentityNode(Graph* graph, const string& node_name, + DataType dtype, const Node* input, + absl::optional requested_device); + } // namespace tensorflow #endif // TENSORFLOW_COMPILER_TF2XLA_TF2XLA_UTIL_H_ -- GitLab From 6cd6115a7be5d9d87ab9ecaa5c75c6270c9061a7 Mon Sep 17 00:00:00 2001 From: Gunhan Gulsoy Date: Wed, 17 Oct 2018 13:02:06 -0700 Subject: [PATCH 0215/1825] Automated rollback of commit a98b34414f125db1edc97bd7e62cf93701ecdf73 PiperOrigin-RevId: 217572495 --- tensorflow/core/framework/op_kernel.cc | 55 +++++++++++++++++++++++++- tools/bazel.rc | 3 +- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/tensorflow/core/framework/op_kernel.cc b/tensorflow/core/framework/op_kernel.cc index 3e34bf0418..c26214c857 100644 --- a/tensorflow/core/framework/op_kernel.cc +++ b/tensorflow/core/framework/op_kernel.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/framework/op_kernel.h" +#include #include #include #include @@ -38,6 +39,7 @@ limitations under the License. #include "tensorflow/core/lib/io/path.h" #include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/lib/strings/strcat.h" +#include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/logging.h" #include "tensorflow/core/platform/mutex.h" #include "tensorflow/core/platform/types.h" @@ -924,12 +926,52 @@ struct KernelRegistration { // KernelDef. typedef std::unordered_multimap KernelRegistry; +#if defined(_WIN32) +static const char kKernelLibPattern[] = "libtfkernel*.dll"; +#elif defined(__APPLE__) +static const char kKernelLibPattern[] = "libtfkernel*.dylib"; +#else +static const char kKernelLibPattern[] = "libtfkernel*.so"; +#endif + +void LoadDynamicKernelsInternal() { + Env* env = Env::Default(); + string bazel_kernel_dir = io::JoinPath(env->GetRunfilesDir(), + "tensorflow", + "core", + "kernels"); + std::vector files; + Status s_kernel_dir = env->GetChildren(bazel_kernel_dir, &files); + if (s_kernel_dir.ok()) { + string dll_spec = io::JoinPath(bazel_kernel_dir, kKernelLibPattern); + for (const auto& file : files) { + string fullpath = io::JoinPath(bazel_kernel_dir, file); + if (env->MatchPath(fullpath, dll_spec)) { + // TODO(gunan): Store the handles to the opened files. + void* unused_filehandle; + TF_CHECK_OK(env->LoadLibrary(fullpath.c_str(), &unused_filehandle)); + } + } + } +} + +// Mechanism for loading existing kernel libraries. +void LoadDynamicKernels() { + // TODO(gunan): As more features are available, add intelligent kernel + // selection, and dropping unsuitable kernel logic here. + static std::once_flag dll_loader_flag; + std::call_once(dll_loader_flag, LoadDynamicKernelsInternal); +} + void* GlobalKernelRegistry() { static KernelRegistry* global_kernel_registry = new KernelRegistry; return global_kernel_registry; } static KernelRegistry* GlobalKernelRegistryTyped() { +#ifdef AUTOLOAD_DYNAMIC_KERNELS + LoadDynamicKernels(); +#endif // AUTOLOAD_DYNAMIC_KERNELS return reinterpret_cast(GlobalKernelRegistry()); } @@ -949,8 +991,17 @@ void OpKernelRegistrar::InitInternal(const KernelDef* kernel_def, const string key = Key(kernel_def->op(), DeviceType(kernel_def->device_type()), kernel_def->label()); - GlobalKernelRegistryTyped()->insert(std::make_pair( - key, KernelRegistration(*kernel_def, kernel_class_name, factory))); + + // To avoid calling LoadDynamicKernels DO NOT CALL GlobalKernelRegistryTyped + // here. + // InitInternal gets called by static initializers, so it ends up executing + // before main. This causes LoadKernelLibraries function to get called + // before some file libraries can initialize, which in turn crashes the + // program flakily. Until we get rid of static initializers in kernel + // registration mechanism, we have this workaround here. + reinterpret_cast(GlobalKernelRegistry()) + ->insert(std::make_pair( + key, KernelRegistration(*kernel_def, kernel_class_name, factory))); } delete kernel_def; } diff --git a/tools/bazel.rc b/tools/bazel.rc index cee0b0b064..a9c86fd485 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -29,7 +29,7 @@ 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_dnn_only=true build:mkl_open_source_only --define=build_with_mkl=true --define=enable_mkl=true build:download_clang --crosstool_top=@local_config_download_clang//:toolchain @@ -86,6 +86,7 @@ build --define=grpc_no_ares=true # Modular TF build options build:dynamic_kernels --define=dynamic_loaded_kernels=true +build:dynamic_kernels -DAUTOLOAD_DYNAMIC_KERNELS # Default paths for TF_SYSTEM_LIBS build --define=PREFIX=/usr -- GitLab From 74fe2db1a54e3af087d4690c801901df280699ab Mon Sep 17 00:00:00 2001 From: Thor Johnsen Date: Wed, 17 Oct 2018 13:14:10 -0700 Subject: [PATCH 0216/1825] clang-format --- .../core/kernels/crop_resize_bilinear_core.h | 2685 ++++++++--------- 1 file changed, 1292 insertions(+), 1393 deletions(-) diff --git a/tensorflow/core/kernels/crop_resize_bilinear_core.h b/tensorflow/core/kernels/crop_resize_bilinear_core.h index 6167cafea2..c57131fd18 100644 --- a/tensorflow/core/kernels/crop_resize_bilinear_core.h +++ b/tensorflow/core/kernels/crop_resize_bilinear_core.h @@ -37,8 +37,8 @@ namespace { // Compute the interpolation indices only once. struct CachedInterpolation { - int lower; // Lower source index used in the interpolation - int upper; // Upper source index used in the interpolation + int lower; // Lower source index used in the interpolation + int upper; // Upper source index used in the interpolation // 1-D linear iterpolation scale (see: // https://en.wikipedia.org/wiki/Bilinear_interpolation) float lerp; @@ -48,7 +48,7 @@ bool compute_single_interpolation_weight(const int in_size, const float out2in_scale, const float out2in_start, const bool clip, const int i, - int* lower, int* upper, float* lerp) { + int *lower, int *upper, float *lerp) { const float in = i * out2in_scale + out2in_start; *lower = (int)floor(in); *upper = (int)ceil(in); @@ -76,7 +76,7 @@ bool compute_single_interpolation_weight(const int in_size, bool compute_interpolation_weights(const int min_i, const int max_i, const int in_size, const float out2in_scale, const float out2in_start, const bool clip, - CachedInterpolation* interpolation) { + CachedInterpolation *interpolation) { bool rval = true; int num_i = max_i - min_i + 1; for (int i = 0; i < num_i; ++i) { @@ -94,16 +94,15 @@ bool compute_interpolation_weights(const int min_i, const int max_i, */ void compute_interpolation_weights(const int out_size, const int in_size, const float out2in_scale, - CachedInterpolation* interpolation) { + CachedInterpolation *interpolation) { interpolation[out_size].lower = 0; interpolation[out_size].upper = 0; const bool clip = true; if (!compute_interpolation_weights(0, out_size - 1, in_size, out2in_scale, 0.0f, clip, interpolation)) { // Should never happen, check for it anyway - printf( - "Warning! Interpolation values have lower,upper indexes outside of " - "range [0,in_size-1]\n"); + printf("Warning! Interpolation values have lower,upper indexes outside of " + "range [0,in_size-1]\n"); } } /** @@ -115,7 +114,7 @@ void compute_interpolation_weights(const int out_size, const int in_size, */ bool compute_minmax_indexes(const int out_size, const int in_size, const float out2in_scale, const float out2in_start, - int* min_i, int* max_i) { + int *min_i, int *max_i) { *min_i = out_size; *max_i = -1; int lower, upper; @@ -123,8 +122,10 @@ bool compute_minmax_indexes(const int out_size, const int in_size, for (int i = 0; i < out_size; ++i) { if (compute_single_interpolation_weight(in_size, out2in_scale, out2in_start, false, i, &lower, &upper, &lerp)) { - if (i < *min_i) *min_i = i; - if (i > *max_i) *max_i = i; + if (i < *min_i) + *min_i = i; + if (i > *max_i) + *max_i = i; } } return (*min_i <= *max_i) ? true : false; @@ -136,9 +137,9 @@ bool compute_minmax_indexes(const int out_size, const int in_size, */ bool compute_interpolation_weights( const int out_size, const int in_size, - const float x1, // lower bounding box, crop region starts at in_size*x1 - const float x2, // upper bounding box, crop region ends at in_size*x2 - int* min_i, int* max_i, std::vector* interpolation) { + const float x1, // lower bounding box, crop region starts at in_size*x1 + const float x2, // upper bounding box, crop region ends at in_size*x2 + int *min_i, int *max_i, std::vector *interpolation) { float out2in_start = out_size > 1 ? (float)(in_size - 1) * (float)x1 : (float)(in_size - 1) * (float)(x1 + x2) / 2.0f; @@ -206,24 +207,24 @@ float compute_lerp(const float top_left, const float top_right, * Optionally flips horizontal and/or vertical axis. */ template -void crop_resize_single_image(const T* image, const int64 in_height, +void crop_resize_single_image(const T *image, const int64 in_height, const int64 in_width, const int64 out_height, const int64 out_width, const int channels, const int min_ix, const int max_ix, - const CachedInterpolation* xs, const int min_iy, - const int max_iy, const CachedInterpolation* ys, + const CachedInterpolation *xs, const int min_iy, + const int max_iy, const CachedInterpolation *ys, const float extrapolated_value, const bool flip_x, const bool flip_y, - U* output) TF_ATTRIBUTE_NOINLINE; + U *output) TF_ATTRIBUTE_NOINLINE; template -void crop_resize_single_image(const T* image, const int64 in_height, +void crop_resize_single_image(const T *image, const int64 in_height, const int64 in_width, const int64 out_height, const int64 out_width, const int channels, const int min_ix, const int max_ix, - const CachedInterpolation* xs, const int min_iy, - const int max_iy, const CachedInterpolation* ys, + const CachedInterpolation *xs, const int min_iy, + const int max_iy, const CachedInterpolation *ys, const float extrapolated_value, const bool flip_x, - const bool flip_y, U* output) { + const bool flip_y, U *output) { const int64 in_row_size = in_width * channels; const int64 out_row_size = out_width * channels; U u_min_val = std::numeric_limits::min(); @@ -234,22 +235,24 @@ void crop_resize_single_image(const T* image, const int64 in_height, cast_to(extrapolated_value, min_val, max_val, u_min_val, u_max_val); // low y extrapolation zone if (min_iy > 0) { - U* p = flip_y ? output + out_row_size * (out_height - min_iy) : output; + U *p = flip_y ? output + out_row_size * (out_height - min_iy) : output; int64 nn = out_row_size * (int64)min_iy; - for (int64 i = 0; i < nn; ++i) p[i] = uEx; + for (int64 i = 0; i < nn; ++i) + p[i] = uEx; } // high y extrapolation zone if (max_iy < out_height - 1) { - U* p = flip_y ? output : output + out_row_size * (max_iy + 1); + U *p = flip_y ? output : output + out_row_size * (max_iy + 1); int64 nn = out_row_size * (int64)(out_height - 1 - max_iy); - for (int64 i = 0; i < nn; ++i) p[i] = uEx; + for (int64 i = 0; i < nn; ++i) + p[i] = uEx; } // low x extrapolation zone if (min_ix > 0) { for (int iy = min_iy; iy <= max_iy; ++iy) { int xx0 = flip_x ? (out_width - min_ix) * channels : 0; int nxx = min_ix * channels; - U* p = output + xx0 + + U *p = output + xx0 + out_row_size * (int64)(flip_y ? out_height - 1 - iy : iy); for (int ix = 0; ix < nxx; ++ix) { p[ix] = uEx; @@ -261,22 +264,22 @@ void crop_resize_single_image(const T* image, const int64 in_height, for (int iy = min_iy; iy <= max_iy; ++iy) { int xx0 = flip_x ? 0 : (max_ix + 1) * channels; int nxx = (out_width - 1 - max_ix) * channels; - U* p = output + xx0 + + U *p = output + xx0 + out_row_size * (int64)(flip_y ? out_height - 1 - iy : iy); for (int ix = 0; ix < nxx; ++ix) { p[ix] = uEx; } } } - U* output_y_ptr = + U *output_y_ptr = output + out_row_size * (int64)(flip_y ? out_height - 1 - min_iy : min_iy); // interpolation zone if (channels == 1) { for (int y = min_iy; y <= max_iy; ++y) { const int iy = y - min_iy; - const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; - const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const T *ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T *ys_input_upper_ptr = image + ys[iy].upper * in_row_size; const float ys_lerp = ys[iy].lerp; const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; @@ -304,8 +307,8 @@ void crop_resize_single_image(const T* image, const int64 in_height, } else if (channels == 2) { for (int y = min_iy; y <= max_iy; ++y) { const int iy = y - min_iy; - const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; - const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const T *ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T *ys_input_upper_ptr = image + ys[iy].upper * in_row_size; const float ys_lerp = ys[iy].lerp; const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; @@ -343,8 +346,8 @@ void crop_resize_single_image(const T* image, const int64 in_height, } else if (channels == 3) { for (int y = min_iy; y <= max_iy; ++y) { const int iy = y - min_iy; - const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; - const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const T *ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T *ys_input_upper_ptr = image + ys[iy].upper * in_row_size; const float ys_lerp = ys[iy].lerp; const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; @@ -392,8 +395,8 @@ void crop_resize_single_image(const T* image, const int64 in_height, } else if (channels == 4) { for (int y = min_iy; y <= max_iy; ++y) { const int iy = y - min_iy; - const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; - const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const T *ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T *ys_input_upper_ptr = image + ys[iy].upper * in_row_size; const float ys_lerp = ys[iy].lerp; const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; @@ -451,8 +454,8 @@ void crop_resize_single_image(const T* image, const int64 in_height, } else { for (int y = min_iy; y <= max_iy; ++y) { const int iy = y - min_iy; - const T* ys_input_lower_ptr = image + ys[iy].lower * in_row_size; - const T* ys_input_upper_ptr = image + ys[iy].upper * in_row_size; + const T *ys_input_lower_ptr = image + ys[iy].lower * in_row_size; + const T *ys_input_upper_ptr = image + ys[iy].upper * in_row_size; const float ys_lerp = ys[iy].lerp; const int x0 = flip_x ? out_width - 1 - max_ix : min_ix; const int x1 = flip_x ? out_width - 1 - min_ix : max_ix; @@ -483,12 +486,12 @@ void crop_resize_single_image(const T* image, const int64 in_height, // machine you are running on template void crop_resize_single_image_common( - const T* image, const int64 in_height, const int64 in_width, + const T *image, const int64 in_height, const int64 in_width, const int64 out_height, const int64 out_width, const int channels, - const int min_ix, const int max_ix, const CachedInterpolation* xs, - const int min_iy, const int max_iy, const CachedInterpolation* ys, + const int min_ix, const int max_ix, const CachedInterpolation *xs, + const int min_iy, const int max_iy, const CachedInterpolation *ys, const float extrapolated_value, const bool flip_x, const bool flip_y, - U* output) TF_ATTRIBUTE_NOINLINE; + U *output) TF_ATTRIBUTE_NOINLINE; // For now, only compile vectorized code on LINUX systems. // to-do: Test vectorized code on other platforms (MacOS and Windows). @@ -515,9 +518,8 @@ void crop_resize_single_image_common( // Eigen::half, bfloat16 or float. // -template -class VectorLoader { - public: +template class VectorLoader { +public: #ifdef __AVX2__ // convert 8 packed words of type T to fp32. // T must be one of uint8, int8, uint16, int16, int32, Eigen::half, bfloat16 @@ -535,20 +537,20 @@ class VectorLoader { // separate 128 bit lanes. // input is stored in lower portion of 4 separate sse words, v0 through v3. // output is stored in lower portion of v0. - void pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); // output is stored in lower portion of v0 and v1. - void pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); // output is stored in lower portion of v0, v1 and v2. - void pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); #else // pack 4 pixels with 1 channel, 2 channels and 3channels respectively. // input is stored in lower portion of 4 separate sse words, v0 through v3. // output is stored in lower portion of v0. - void pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); // output is stored in lower portion of v0 and v1. - void pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); // output is stored in lower portion of v0, v1 and v2. - void pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); #endif #ifdef __AVX2__ @@ -572,8 +574,8 @@ class VectorLoader { // pixels have 1 channel. // load1 case, i.e. 4 left and right inputs are loaded with a single unaligned // SSE load. - void load1_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m256* left0, __m256* right0); + void load1_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m256 *left0, __m256 *right0); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -581,9 +583,9 @@ class VectorLoader { // pixels have 2 channels. // load1 case, i.e. 4 left and right inputs are loaded with a single unaligned // SSE load. - void load1_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m256* left0, __m256* left1, - __m256* right0, __m256* right1); + void load1_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m256 *left0, __m256 *left1, + __m256 *right0, __m256 *right1); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -591,9 +593,9 @@ class VectorLoader { // pixels have 3 channels. // load1 case, i.e. 4 left and right inputs are loaded with a single unaligned // SSE load. - void load1_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m256* left0, __m256* left1, - __m256* left2, __m256* right0, __m256* right1, __m256* right2); + void load1_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m256 *left0, __m256 *left1, + __m256 *left2, __m256 *right0, __m256 *right1, __m256 *right2); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -601,10 +603,10 @@ class VectorLoader { // pixels have 4 channels. // load1 case, i.e. 4 left and right inputs are loaded with a single unaligned // SSE load. - void load1_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m256* left0, __m256* left1, - __m256* left2, __m256* left3, __m256* right0, __m256* right1, - __m256* right2, __m256* right3); + void load1_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m256 *left0, __m256 *left1, + __m256 *left2, __m256 *left3, __m256 *right0, __m256 *right1, + __m256 *right2, __m256 *right3); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -612,8 +614,8 @@ class VectorLoader { // pixels have 1 channel. // load2 case, i.e. 4 left inputs are loaded with first SSE load and 4 right // inputs are loaded with second SSE load. - void load2_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m256* left0, __m256* right0); + void load2_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m256 *left0, __m256 *right0); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -621,9 +623,9 @@ class VectorLoader { // pixels have 2 channels. // load2 case, i.e. 4 left inputs are loaded with first SSE load and 4 right // inputs are loaded with second SSE load. - void load2_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m256* left0, __m256* left1, - __m256* right0, __m256* right1); + void load2_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m256 *left0, __m256 *left1, + __m256 *right0, __m256 *right1); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -631,9 +633,9 @@ class VectorLoader { // pixels have 3 channels. // load2 case, i.e. 4 left inputs are loaded with first SSE load and 4 right // inputs are loaded with second SSE load. - void load2_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m256* left0, __m256* left1, - __m256* left2, __m256* right0, __m256* right1, __m256* right2); + void load2_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m256 *left0, __m256 *left1, + __m256 *left2, __m256 *right0, __m256 *right1, __m256 *right2); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -641,10 +643,10 @@ class VectorLoader { // pixels have 4 channels. // load2 case, i.e. 4 left inputs are loaded with first SSE load and 4 right // inputs are loaded with second SSE load. - void load2_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m256* left0, __m256* left1, - __m256* left2, __m256* left3, __m256* right0, __m256* right1, - __m256* right2, __m256* right3); + void load2_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m256 *left0, __m256 *left1, + __m256 *left2, __m256 *left3, __m256 *right0, __m256 *right1, + __m256 *right2, __m256 *right3); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -652,9 +654,9 @@ class VectorLoader { // pixels have 1 channel. // load4 case, i.e. each pair of left and right inputs are loaded with a // separate SSE load. - void load4_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m256* left0, - __m256* right0); + void load4_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256 *left0, + __m256 *right0); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -662,9 +664,9 @@ class VectorLoader { // pixels have 2 channels. // load4 case, i.e. each pair of left and right inputs are loaded with a // separate SSE load. - void load4_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m256* left0, - __m256* left1, __m256* right0, __m256* right1); + void load4_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256 *left0, + __m256 *left1, __m256 *right0, __m256 *right1); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -672,10 +674,10 @@ class VectorLoader { // pixels have 3 channels. // load4 case, i.e. each pair of left and right inputs are loaded with a // separate SSE load. - void load4_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m256* left0, - __m256* left1, __m256* left2, __m256* right0, __m256* right1, - __m256* right2); + void load4_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256 *left0, + __m256 *left1, __m256 *left2, __m256 *right0, __m256 *right1, + __m256 *right2); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -683,10 +685,10 @@ class VectorLoader { // pixels have 4 channels. // load4 case, i.e. each pair of left and right inputs are loaded with a // separate SSE load. - void load4_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m256* left0, - __m256* left1, __m256* left2, __m256* left3, __m256* right0, - __m256* right1, __m256* right2, __m256* right3); + void load4_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256 *left0, + __m256 *left1, __m256 *left2, __m256 *left3, __m256 *right0, + __m256 *right1, __m256 *right2, __m256 *right3); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -695,9 +697,9 @@ class VectorLoader { // load8 case, i.e. each input is loaded with a separate SSE load. // 4 pixels, each with left and right input necessitates 8 separate SSE loads // per input row. - void load8_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m256* left0, - __m256* right0); + void load8_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256 *left0, + __m256 *right0); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -706,9 +708,9 @@ class VectorLoader { // load8 case, i.e. each input is loaded with a separate SSE load. // 4 pixels, each with left and right input necessitates 8 separate SSE loads // per input row. - void load8_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m256* left0, - __m256* left1, __m256* right0, __m256* right1); + void load8_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256 *left0, + __m256 *left1, __m256 *right0, __m256 *right1); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -717,10 +719,10 @@ class VectorLoader { // load8 case, i.e. each input is loaded with a separate SSE load. // 4 pixels, each with left and right input necessitates 8 separate SSE loads // per input row. - void load8_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m256* left0, - __m256* left1, __m256* left2, __m256* right0, __m256* right1, - __m256* right2); + void load8_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256 *left0, + __m256 *left1, __m256 *left2, __m256 *right0, __m256 *right1, + __m256 *right2); // load top left and bottom left interpolation inputs into output argument // left. // load top right and bottom right interpolation inputs into output argument @@ -729,10 +731,10 @@ class VectorLoader { // load8 case, i.e. each input is loaded with a separate SSE load. // 4 pixels, each with left and right input necessitates 8 separate SSE loads // per input row. - void load8_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m256* left0, - __m256* left1, __m256* left2, __m256* left3, __m256* right0, - __m256* right1, __m256* right2, __m256* right3); + void load8_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m256 *left0, + __m256 *left1, __m256 *left2, __m256 *left3, __m256 *right0, + __m256 *right1, __m256 *right2, __m256 *right3); #else // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. @@ -741,9 +743,9 @@ class VectorLoader { // pixels have 1 channel. // load1 case, i.e. all inputs for one input row are loaded with a single SSE // load. - void load1_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m128* tl0, __m128* bl0, - __m128* tr0, __m128* br0); + void load1_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m128 *tl0, __m128 *bl0, + __m128 *tr0, __m128 *br0); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -751,10 +753,10 @@ class VectorLoader { // pixels have 2 channels. // load1 case, i.e. all inputs for one input row are loaded with a single SSE // load. - void load1_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, - __m128* bl0, __m128* bl1, __m128* tr0, __m128* tr1, - __m128* br0, __m128* br1); + void load1_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m128 *tl0, __m128 *tl1, + __m128 *bl0, __m128 *bl1, __m128 *tr0, __m128 *tr1, + __m128 *br0, __m128 *br1); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -762,11 +764,11 @@ class VectorLoader { // pixels have 3 channels. // load1 case, i.e. all inputs for one input row are loaded with a single SSE // load. - void load1_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, - __m128* tl2, __m128* bl0, __m128* bl1, __m128* bl2, - __m128* tr0, __m128* tr1, __m128* tr2, __m128* br0, - __m128* br1, __m128* br2); + void load1_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m128 *tl0, __m128 *tl1, + __m128 *tl2, __m128 *bl0, __m128 *bl1, __m128 *bl2, + __m128 *tr0, __m128 *tr1, __m128 *tr2, __m128 *br0, + __m128 *br1, __m128 *br2); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -774,12 +776,12 @@ class VectorLoader { // pixels have 4 channels. // load1 case, i.e. all inputs for one input row are loaded with a single SSE // load. - void load1_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, - __m128* tl2, __m128* tl3, __m128* bl0, __m128* bl1, - __m128* bl2, __m128* bl3, __m128* tr0, __m128* tr1, - __m128* tr2, __m128* tr3, __m128* br0, __m128* br1, - __m128* br2, __m128* br3); + void load1_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m128 *tl0, __m128 *tl1, + __m128 *tl2, __m128 *tl3, __m128 *bl0, __m128 *bl1, + __m128 *bl2, __m128 *bl3, __m128 *tr0, __m128 *tr1, + __m128 *tr2, __m128 *tr3, __m128 *br0, __m128 *br1, + __m128 *br2, __m128 *br3); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -787,9 +789,9 @@ class VectorLoader { // pixels have 1 channel. // load2 case, i.e. left inputs are loaded with first SSE load, right inputs // are loaded with second SSE load. - void load2_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m128* tl0, __m128* bl0, - __m128* tr0, __m128* br0); + void load2_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m128 *tl0, __m128 *bl0, + __m128 *tr0, __m128 *br0); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -797,10 +799,10 @@ class VectorLoader { // pixels have 2 channels. // load2 case, i.e. left inputs are loaded with first SSE load, right inputs // are loaded with second SSE load. - void load2_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, - __m128* bl0, __m128* bl1, __m128* tr0, __m128* tr1, - __m128* br0, __m128* br1); + void load2_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m128 *tl0, __m128 *tl1, + __m128 *bl0, __m128 *bl1, __m128 *tr0, __m128 *tr1, + __m128 *br0, __m128 *br1); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -808,11 +810,11 @@ class VectorLoader { // pixels have 3 channels. // load2 case, i.e. left inputs are loaded with first SSE load, right inputs // are loaded with second SSE load. - void load2_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, - __m128* tl2, __m128* bl0, __m128* bl1, __m128* bl2, - __m128* tr0, __m128* tr1, __m128* tr2, __m128* br0, - __m128* br1, __m128* br2); + void load2_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m128 *tl0, __m128 *tl1, + __m128 *tl2, __m128 *bl0, __m128 *bl1, __m128 *bl2, + __m128 *tr0, __m128 *tr1, __m128 *tr2, __m128 *br0, + __m128 *br1, __m128 *br2); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -820,12 +822,12 @@ class VectorLoader { // pixels have 4 channels. // load2 case, i.e. left inputs are loaded with first SSE load, right inputs // are loaded with second SSE load. - void load2_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, - const __m128i* shuffle_masks, __m128* tl0, __m128* tl1, - __m128* tl2, __m128* tl3, __m128* bl0, __m128* bl1, - __m128* bl2, __m128* bl3, __m128* tr0, __m128* tr1, - __m128* tr2, __m128* tr3, __m128* br0, __m128* br1, - __m128* br2, __m128* br3); + void load2_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, + const __m128i *shuffle_masks, __m128 *tl0, __m128 *tl1, + __m128 *tl2, __m128 *tl3, __m128 *bl0, __m128 *bl1, + __m128 *bl2, __m128 *bl3, __m128 *tr0, __m128 *tr1, + __m128 *tr2, __m128 *tr3, __m128 *br0, __m128 *br1, + __m128 *br2, __m128 *br3); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -833,9 +835,9 @@ class VectorLoader { // pixels have 1 channel. // load4 case, i.e. left and right inputs are loaded with a separate SSE load // for each pixel. - void load4_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m128* tl0, - __m128* bl0, __m128* tr0, __m128* br0); + void load4_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128 *tl0, + __m128 *bl0, __m128 *tr0, __m128 *br0); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -843,10 +845,10 @@ class VectorLoader { // pixels have 2 channels. // load4 case, i.e. left and right inputs are loaded with a separate SSE load // for each pixel. - void load4_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m128* tl0, - __m128* tl1, __m128* bl0, __m128* bl1, __m128* tr0, - __m128* tr1, __m128* br0, __m128* br1); + void load4_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128 *tl0, + __m128 *tl1, __m128 *bl0, __m128 *bl1, __m128 *tr0, + __m128 *tr1, __m128 *br0, __m128 *br1); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -854,11 +856,11 @@ class VectorLoader { // pixels have 3 channels. // load4 case, i.e. left and right inputs are loaded with a separate SSE load // for each pixel. - void load4_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m128* tl0, - __m128* tl1, __m128* tl2, __m128* bl0, __m128* bl1, - __m128* bl2, __m128* tr0, __m128* tr1, __m128* tr2, - __m128* br0, __m128* br1, __m128* br2); + void load4_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128 *tl0, + __m128 *tl1, __m128 *tl2, __m128 *bl0, __m128 *bl1, + __m128 *bl2, __m128 *tr0, __m128 *tr1, __m128 *tr2, + __m128 *br0, __m128 *br1, __m128 *br2); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -866,12 +868,12 @@ class VectorLoader { // pixels have 4 channels. // load4 case, i.e. left and right inputs are loaded with a separate SSE load // for each pixel. - void load4_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m128* tl0, - __m128* tl1, __m128* tl2, __m128* tl3, __m128* bl0, - __m128* bl1, __m128* bl2, __m128* bl3, __m128* tr0, - __m128* tr1, __m128* tr2, __m128* tr3, __m128* br0, - __m128* br1, __m128* br2, __m128* br3); + void load4_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128 *tl0, + __m128 *tl1, __m128 *tl2, __m128 *tl3, __m128 *bl0, + __m128 *bl1, __m128 *bl2, __m128 *bl3, __m128 *tr0, + __m128 *tr1, __m128 *tr2, __m128 *tr3, __m128 *br0, + __m128 *br1, __m128 *br2, __m128 *br3); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -879,9 +881,9 @@ class VectorLoader { // pixels have 1 channel. // load8 case, i.e. left and right inputs are loaded with separate SSE loads // for each pixel. - void load8_1ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m128* tl0, - __m128* bl0, __m128* tr0, __m128* br0); + void load8_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128 *tl0, + __m128 *bl0, __m128 *tr0, __m128 *br0); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -889,10 +891,10 @@ class VectorLoader { // pixels have 2 channels. // load8 case, i.e. left and right inputs are loaded with separate SSE loads // for each pixel. - void load8_2ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m128* tl0, - __m128* tl1, __m128* bl0, __m128* bl1, __m128* tr0, - __m128* tr1, __m128* br0, __m128* br1); + void load8_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128 *tl0, + __m128 *tl1, __m128 *bl0, __m128 *bl1, __m128 *tr0, + __m128 *tr1, __m128 *br0, __m128 *br1); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -900,11 +902,11 @@ class VectorLoader { // pixels have 3 channels. // load8 case, i.e. left and right inputs are loaded with separate SSE loads // for each pixel. - void load8_3ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m128* tl0, - __m128* tl1, __m128* tl2, __m128* bl0, __m128* bl1, - __m128* bl2, __m128* tr0, __m128* tr1, __m128* tr2, - __m128* br0, __m128* br1, __m128* br2); + void load8_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128 *tl0, + __m128 *tl1, __m128 *tl2, __m128 *bl0, __m128 *bl1, + __m128 *bl2, __m128 *tr0, __m128 *tr1, __m128 *tr2, + __m128 *br0, __m128 *br1, __m128 *br2); // load top left interpolation inputs into output argument tl. // load bottom left interpolation inputs into output argument bl. // load top right interpolation inputs into output argument tr. @@ -912,48 +914,48 @@ class VectorLoader { // pixels have 4 channels. // load8 case, i.e. left and right inputs are loaded with separate SSE loads // for each pixel. - void load8_4ch(const T* lower_ptr, const T* upper_ptr, int offset0, - int offset1, int offset2, int offset3, __m128* tl0, - __m128* tl1, __m128* tl2, __m128* tl3, __m128* bl0, - __m128* bl1, __m128* bl2, __m128* bl3, __m128* tr0, - __m128* tr1, __m128* tr2, __m128* tr3, __m128* br0, - __m128* br1, __m128* br2, __m128* br3); + void load8_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, + int offset1, int offset2, int offset3, __m128 *tl0, + __m128 *tl1, __m128 *tl2, __m128 *tl3, __m128 *bl0, + __m128 *bl1, __m128 *bl2, __m128 *bl3, __m128 *tr0, + __m128 *tr1, __m128 *tr2, __m128 *tr3, __m128 *br0, + __m128 *br1, __m128 *br2, __m128 *br3); #endif // there is no method that packs 4 pixels with 4 channel into four sse words. // nothing to do for this case, everything is already in the right position. - private: +private: // helper methods #ifdef __AVX2__ // pack 4 pixels with 1, 2, 3 or 4 channels into lower portion of SSE vector // word. // works within SSE lanes. // sizeof(sample_data_type) can be 1, 2 or 4 bytes. - void pack4_1b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); - void pack4_2b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); - void pack4_4b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); - void pack4_1b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); - void pack4_2b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); - void pack4_4b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); - void pack4_1b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); - void pack4_2b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); - void pack4_4b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, __m256i* v3); + void pack4_1b_1ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); + void pack4_2b_1ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); + void pack4_4b_1ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); + void pack4_1b_2ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); + void pack4_2b_2ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); + void pack4_4b_2ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); + void pack4_1b_3ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); + void pack4_2b_3ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); + void pack4_4b_3ch_(__m256i *v0, __m256i *v1, __m256i *v2, __m256i *v3); // there is no pack4_xx_4ch functions because none is needed. // all the bytes are loaded in the right spots for this case. #else // pack 4 pixels with 1, 2, 3 or 4 channels into lower portion of SSE vector // word. // sizeof(sample_data_type) can be 1, 2 or 4 bytes. - void pack4_1b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); - void pack4_2b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); - void pack4_4b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); - void pack4_1b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); - void pack4_2b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); - void pack4_4b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); - void pack4_1b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); - void pack4_2b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); - void pack4_4b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, __m128i* v3); + void pack4_1b_1ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); + void pack4_2b_1ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); + void pack4_4b_1ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); + void pack4_1b_2ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); + void pack4_2b_2ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); + void pack4_4b_2ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); + void pack4_1b_3ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); + void pack4_2b_3ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); + void pack4_4b_3ch_(__m128i *v0, __m128i *v1, __m128i *v2, __m128i *v3); #endif #ifdef __AVX2__ __m256i extract_right_1b_(const __m256i left); @@ -974,8 +976,8 @@ class VectorLoader { #ifdef __AVX2__ template -void VectorLoader::pack4_1b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_1b_1ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { *v3 = _mm256_slli_si256(*v3, 3); __m256i and_mask = _mm256_setr_epi32(255, 0, 0, 0, 255, 0, 0, 0); *v2 = _mm256_or_si256(*v3, @@ -985,8 +987,8 @@ void VectorLoader::pack4_1b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, *v0 = _mm256_or_si256(*v1, _mm256_and_si256(and_mask, *v0)); } template -void VectorLoader::pack4_2b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_2b_1ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { *v3 = _mm256_slli_si256(*v3, 6); __m256i and_mask = _mm256_setr_epi32(65535, 0, 0, 0, 65535, 0, 0, 0); *v2 = _mm256_or_si256(*v3, @@ -996,8 +998,8 @@ void VectorLoader::pack4_2b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, *v0 = _mm256_or_si256(*v1, _mm256_and_si256(and_mask, *v0)); } template -void VectorLoader::pack4_4b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_4b_1ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { *v3 = _mm256_slli_si256(*v3, 12); __m256i and_mask = _mm256_setr_epi32(-1, 0, 0, 0, -1, 0, 0, 0); *v2 = _mm256_or_si256(*v3, @@ -1008,8 +1010,8 @@ void VectorLoader::pack4_4b_1ch_(__m256i* v0, __m256i* v1, __m256i* v2, } template -void VectorLoader::pack4_1b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_1b_2ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { __m256i and_mask = _mm256_setr_epi32(65535, 0, 0, 0, 65535, 0, 0, 0); *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), _mm256_slli_si256(*v1, 2)); @@ -1017,8 +1019,8 @@ void VectorLoader::pack4_1b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, _mm256_slli_si256(*v3, 2)); } template -void VectorLoader::pack4_2b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_2b_2ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { __m256i and_mask = _mm256_setr_epi32(-1, 0, 0, 0, -1, 0, 0, 0); *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), _mm256_slli_si256(*v1, 4)); @@ -1026,8 +1028,8 @@ void VectorLoader::pack4_2b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, _mm256_slli_si256(*v3, 4)); } template -void VectorLoader::pack4_4b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_4b_2ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { __m256i and_mask = _mm256_setr_epi32(-1, -1, 0, 0, -1, -1, 0, 0); *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), _mm256_slli_si256(*v1, 8)); @@ -1036,8 +1038,8 @@ void VectorLoader::pack4_4b_2ch_(__m256i* v0, __m256i* v1, __m256i* v2, } template -void VectorLoader::pack4_1b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_1b_3ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { __m256i and_mask = _mm256_setr_epi32(16777215, 0, 0, 0, 16777215, 0, 0, 0); *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), _mm256_slli_si256(*v1, 3)); @@ -1049,8 +1051,8 @@ void VectorLoader::pack4_1b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, _mm256_slli_si256(*v3, 1)); } template -void VectorLoader::pack4_2b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_2b_3ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { __m256i and_mask = _mm256_setr_epi32(-1, 65535, 0, 0, -1, 65535, 0, 0); *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), _mm256_slli_si256(*v1, 6)); @@ -1062,8 +1064,8 @@ void VectorLoader::pack4_2b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, _mm256_slli_si256(*v3, 2)); } template -void VectorLoader::pack4_4b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack4_4b_3ch_(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { __m256i and_mask = _mm256_setr_epi32(-1, -1, -1, 0, -1, -1, -1, 0); *v0 = _mm256_or_si256(_mm256_and_si256(*v0, and_mask), _mm256_slli_si256(*v1, 12)); @@ -1076,131 +1078,131 @@ void VectorLoader::pack4_4b_3ch_(__m256i* v0, __m256i* v1, __m256i* v2, } template <> -void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_1b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_1b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_4b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_1ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_4b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_1b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_1b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_4b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_2ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_4b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_1b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_1b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_4b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_2b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m256i* v0, __m256i* v1, __m256i* v2, - __m256i* v3) { +void VectorLoader::pack_3ch(__m256i *v0, __m256i *v1, __m256i *v2, + __m256i *v3) { pack4_4b_3ch_(v0, v1, v2, v3); } #else template -void VectorLoader::pack4_1b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_1b_1ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { *v3 = _mm_slli_si128(*v3, 3); __m128i and_mask = _mm_setr_epi32(255, 0, 0, 0); *v2 = _mm_or_si128(*v3, _mm_slli_si128(_mm_and_si128(and_mask, *v2), 2)); @@ -1208,8 +1210,8 @@ void VectorLoader::pack4_1b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, *v0 = _mm_or_si128(*v1, _mm_and_si128(and_mask, *v0)); } template -void VectorLoader::pack4_2b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_2b_1ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { *v3 = _mm_slli_si128(*v3, 6); __m128i and_mask = _mm_setr_epi32(65535, 0, 0, 0); *v2 = _mm_or_si128(*v3, _mm_slli_si128(_mm_and_si128(and_mask, *v2), 4)); @@ -1217,8 +1219,8 @@ void VectorLoader::pack4_2b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, *v0 = _mm_or_si128(*v1, _mm_and_si128(and_mask, *v0)); } template -void VectorLoader::pack4_4b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_4b_1ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { *v3 = _mm_slli_si128(*v3, 12); __m128i and_mask = _mm_setr_epi32(-1, 0, 0, 0); *v2 = _mm_or_si128(*v3, _mm_slli_si128(_mm_and_si128(and_mask, *v2), 8)); @@ -1226,29 +1228,29 @@ void VectorLoader::pack4_4b_1ch_(__m128i* v0, __m128i* v1, __m128i* v2, *v0 = _mm_or_si128(*v1, _mm_and_si128(and_mask, *v0)); } template -void VectorLoader::pack4_1b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_1b_2ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { __m128i and_mask = _mm_setr_epi32(65535, 0, 0, 0); *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 2)); *v1 = _mm_or_si128(_mm_and_si128(*v2, and_mask), _mm_slli_si128(*v3, 2)); } template -void VectorLoader::pack4_2b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_2b_2ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { __m128i and_mask = _mm_setr_epi32(-1, 0, 0, 0); *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 4)); *v1 = _mm_or_si128(_mm_and_si128(*v2, and_mask), _mm_slli_si128(*v3, 4)); } template -void VectorLoader::pack4_4b_2ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_4b_2ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { __m128i and_mask = _mm_setr_epi32(-1, -1, 0, 0); *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 8)); *v1 = _mm_or_si128(_mm_and_si128(*v2, and_mask), _mm_slli_si128(*v3, 8)); } template -void VectorLoader::pack4_1b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_1b_3ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { __m128i and_mask = _mm_setr_epi32(16777215, 0, 0, 0); *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 3)); and_mask = _mm_srli_si128(and_mask, 1); @@ -1259,8 +1261,8 @@ void VectorLoader::pack4_1b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, _mm_slli_si128(*v3, 1)); } template -void VectorLoader::pack4_2b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_2b_3ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { __m128i and_mask = _mm_setr_epi32(-1, 65535, 0, 0); *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 6)); and_mask = _mm_srli_si128(and_mask, 2); @@ -1271,8 +1273,8 @@ void VectorLoader::pack4_2b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, _mm_slli_si128(*v3, 2)); } template -void VectorLoader::pack4_4b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack4_4b_3ch_(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { __m128i and_mask = _mm_setr_epi32(-1, -1, -1, 0); *v0 = _mm_or_si128(_mm_and_si128(*v0, and_mask), _mm_slli_si128(*v1, 12)); and_mask = _mm_srli_si128(and_mask, 4); @@ -1284,148 +1286,144 @@ void VectorLoader::pack4_4b_3ch_(__m128i* v0, __m128i* v1, __m128i* v2, } template <> -void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_1b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_1b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_4b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_1ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_1ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_4b_1ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_1b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_1b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_4b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_2ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_2ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_4b_2ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_1b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_1b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_4b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_2b_3ch_(v0, v1, v2, v3); } template <> -void VectorLoader::pack_3ch(__m128i* v0, __m128i* v1, __m128i* v2, - __m128i* v3) { +void VectorLoader::pack_3ch(__m128i *v0, __m128i *v1, __m128i *v2, + __m128i *v3) { pack4_4b_3ch_(v0, v1, v2, v3); } #endif #ifdef __AVX2__ -template <> -__m256i VectorLoader::extract_right_1ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_1ch(const __m256i left) { return extract_right_1b_(left); } -template <> -__m256i VectorLoader::extract_right_1ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_1ch(const __m256i left) { return extract_right_1b_(left); } template <> __m256i VectorLoader::extract_right_1ch(const __m256i left) { return extract_right_2b_(left); } -template <> -__m256i VectorLoader::extract_right_1ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_1ch(const __m256i left) { return extract_right_2b_(left); } -template <> -__m256i VectorLoader::extract_right_1ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_1ch(const __m256i left) { return extract_right_4b_(left); } template <> @@ -1436,29 +1434,24 @@ template <> __m256i VectorLoader::extract_right_1ch(const __m256i left) { return extract_right_2b_(left); } -template <> -__m256i VectorLoader::extract_right_1ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_1ch(const __m256i left) { return extract_right_4b_(left); } -template <> -__m256i VectorLoader::extract_right_2ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_2ch(const __m256i left) { return extract_right_2b_(left); } -template <> -__m256i VectorLoader::extract_right_2ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_2ch(const __m256i left) { return extract_right_2b_(left); } template <> __m256i VectorLoader::extract_right_2ch(const __m256i left) { return extract_right_4b_(left); } -template <> -__m256i VectorLoader::extract_right_2ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_2ch(const __m256i left) { return extract_right_4b_(left); } -template <> -__m256i VectorLoader::extract_right_2ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_2ch(const __m256i left) { return extract_right_8b_(left); } template <> @@ -1469,29 +1462,24 @@ template <> __m256i VectorLoader::extract_right_2ch(const __m256i left) { return extract_right_4b_(left); } -template <> -__m256i VectorLoader::extract_right_2ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_2ch(const __m256i left) { return extract_right_8b_(left); } -template <> -__m256i VectorLoader::extract_right_3ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_3ch(const __m256i left) { return extract_right_3b_(left); } -template <> -__m256i VectorLoader::extract_right_3ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_3ch(const __m256i left) { return extract_right_3b_(left); } template <> __m256i VectorLoader::extract_right_3ch(const __m256i left) { return extract_right_6b_(left); } -template <> -__m256i VectorLoader::extract_right_3ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_3ch(const __m256i left) { return extract_right_6b_(left); } -template <> -__m256i VectorLoader::extract_right_3ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_3ch(const __m256i left) { assert(false); } template <> @@ -1502,29 +1490,24 @@ template <> __m256i VectorLoader::extract_right_3ch(const __m256i left) { return extract_right_6b_(left); } -template <> -__m256i VectorLoader::extract_right_3ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_3ch(const __m256i left) { assert(false); } -template <> -__m256i VectorLoader::extract_right_4ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_4ch(const __m256i left) { return extract_right_4b_(left); } -template <> -__m256i VectorLoader::extract_right_4ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_4ch(const __m256i left) { return extract_right_4b_(left); } template <> __m256i VectorLoader::extract_right_4ch(const __m256i left) { return extract_right_8b_(left); } -template <> -__m256i VectorLoader::extract_right_4ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_4ch(const __m256i left) { return extract_right_8b_(left); } -template <> -__m256i VectorLoader::extract_right_4ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_4ch(const __m256i left) { assert(false); } template <> @@ -1535,29 +1518,24 @@ template <> __m256i VectorLoader::extract_right_4ch(const __m256i left) { return extract_right_8b_(left); } -template <> -__m256i VectorLoader::extract_right_4ch(const __m256i left) { +template <> __m256i VectorLoader::extract_right_4ch(const __m256i left) { assert(false); } #else -template <> -__m128i VectorLoader::extract_right_1ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_1ch(const __m128i left) { return extract_right_1b_(left); } -template <> -__m128i VectorLoader::extract_right_1ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_1ch(const __m128i left) { return extract_right_1b_(left); } template <> __m128i VectorLoader::extract_right_1ch(const __m128i left) { return extract_right_2b_(left); } -template <> -__m128i VectorLoader::extract_right_1ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_1ch(const __m128i left) { return extract_right_2b_(left); } -template <> -__m128i VectorLoader::extract_right_1ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_1ch(const __m128i left) { return extract_right_4b_(left); } template <> @@ -1568,29 +1546,24 @@ template <> __m128i VectorLoader::extract_right_1ch(const __m128i left) { return extract_right_2b_(left); } -template <> -__m128i VectorLoader::extract_right_1ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_1ch(const __m128i left) { return extract_right_4b_(left); } -template <> -__m128i VectorLoader::extract_right_2ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_2ch(const __m128i left) { return extract_right_2b_(left); } -template <> -__m128i VectorLoader::extract_right_2ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_2ch(const __m128i left) { return extract_right_2b_(left); } template <> __m128i VectorLoader::extract_right_2ch(const __m128i left) { return extract_right_4b_(left); } -template <> -__m128i VectorLoader::extract_right_2ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_2ch(const __m128i left) { return extract_right_4b_(left); } -template <> -__m128i VectorLoader::extract_right_2ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_2ch(const __m128i left) { return extract_right_8b_(left); } template <> @@ -1601,29 +1574,24 @@ template <> __m128i VectorLoader::extract_right_2ch(const __m128i left) { return extract_right_4b_(left); } -template <> -__m128i VectorLoader::extract_right_2ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_2ch(const __m128i left) { return extract_right_8b_(left); } -template <> -__m128i VectorLoader::extract_right_3ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_3ch(const __m128i left) { return extract_right_3b_(left); } -template <> -__m128i VectorLoader::extract_right_3ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_3ch(const __m128i left) { return extract_right_3b_(left); } template <> __m128i VectorLoader::extract_right_3ch(const __m128i left) { return extract_right_6b_(left); } -template <> -__m128i VectorLoader::extract_right_3ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_3ch(const __m128i left) { return extract_right_6b_(left); } -template <> -__m128i VectorLoader::extract_right_3ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_3ch(const __m128i left) { assert(false); } template <> @@ -1634,29 +1602,24 @@ template <> __m128i VectorLoader::extract_right_3ch(const __m128i left) { return extract_right_6b_(left); } -template <> -__m128i VectorLoader::extract_right_3ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_3ch(const __m128i left) { assert(false); } -template <> -__m128i VectorLoader::extract_right_4ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_4ch(const __m128i left) { return extract_right_4b_(left); } -template <> -__m128i VectorLoader::extract_right_4ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_4ch(const __m128i left) { return extract_right_4b_(left); } template <> __m128i VectorLoader::extract_right_4ch(const __m128i left) { return extract_right_8b_(left); } -template <> -__m128i VectorLoader::extract_right_4ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_4ch(const __m128i left) { return extract_right_8b_(left); } -template <> -__m128i VectorLoader::extract_right_4ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_4ch(const __m128i left) { assert(false); } template <> @@ -1667,53 +1630,45 @@ template <> __m128i VectorLoader::extract_right_4ch(const __m128i left) { return extract_right_8b_(left); } -template <> -__m128i VectorLoader::extract_right_4ch(const __m128i left) { +template <> __m128i VectorLoader::extract_right_4ch(const __m128i left) { assert(false); } #endif #ifdef __AVX2__ -template <> -__m256 VectorLoader::to_fp32(__m256i raw) { +template <> __m256 VectorLoader::to_fp32(__m256i raw) { raw = _mm256_insertf128_si256( _mm256_castsi128_si256(_mm_cvtepu8_epi32(_mm256_castsi256_si128(raw))), _mm_cvtepu8_epi32(_mm256_extractf128_si256(raw, 1)), 1); return _mm256_cvtepi32_ps(raw); } -template <> -__m256 VectorLoader::to_fp32(__m256i raw) { +template <> __m256 VectorLoader::to_fp32(__m256i raw) { raw = _mm256_insertf128_si256( _mm256_castsi128_si256(_mm_cvtepi8_epi32(_mm256_castsi256_si128(raw))), _mm_cvtepi8_epi32(_mm256_extractf128_si256(raw, 1)), 1); return _mm256_cvtepi32_ps(raw); } -template <> -__m256 VectorLoader::to_fp32(__m256i raw) { +template <> __m256 VectorLoader::to_fp32(__m256i raw) { raw = _mm256_insertf128_si256( _mm256_castsi128_si256(_mm_cvtepu16_epi32(_mm256_castsi256_si128(raw))), _mm_cvtepu16_epi32(_mm256_extractf128_si256(raw, 1)), 1); return _mm256_cvtepi32_ps(raw); } -template <> -__m256 VectorLoader::to_fp32(__m256i raw) { +template <> __m256 VectorLoader::to_fp32(__m256i raw) { raw = _mm256_insertf128_si256( _mm256_castsi128_si256(_mm_cvtepi16_epi32(_mm256_castsi256_si128(raw))), _mm_cvtepi16_epi32(_mm256_extractf128_si256(raw, 1)), 1); return _mm256_cvtepi32_ps(raw); } -template <> -__m256 VectorLoader::to_fp32(__m256i raw) { +template <> __m256 VectorLoader::to_fp32(__m256i raw) { return _mm256_cvtepi32_ps(raw); } -template <> -__m256 VectorLoader::to_fp32(__m256i raw) { +template <> __m256 VectorLoader::to_fp32(__m256i raw) { return _mm256_insertf128_ps( _mm256_castps128_ps256(_mm_cvtph_ps(_mm256_castsi256_si128(raw))), _mm_cvtph_ps(_mm256_extractf128_si256(raw, 1)), 1); } -template <> -__m256 VectorLoader::to_fp32(__m256i raw) { +template <> __m256 VectorLoader::to_fp32(__m256i raw) { // bfloat16 is essentially fp32 with mantissa truncated from 23 to 7 bits. // can convert with << 16, which we fuse with initial shuffle into epi32 // positions. @@ -1722,33 +1677,26 @@ __m256 VectorLoader::to_fp32(__m256i raw) { -128, -128, 0, 1, -128, -128, 2, 3, -128, -128, 4, 5, -128, -128, 6, 7); return _mm256_castsi256_ps(_mm256_shuffle_epi8(raw, shuf_hi32)); } -template <> -__m256 VectorLoader::to_fp32(__m256i raw) { +template <> __m256 VectorLoader::to_fp32(__m256i raw) { return _mm256_castsi256_ps(raw); } #else -template <> -__m128 VectorLoader::to_fp32(__m128i raw) { +template <> __m128 VectorLoader::to_fp32(__m128i raw) { return _mm_cvtepi32_ps(_mm_cvtepu8_epi32(raw)); } -template <> -__m128 VectorLoader::to_fp32(__m128i raw) { +template <> __m128 VectorLoader::to_fp32(__m128i raw) { return _mm_cvtepi32_ps(_mm_cvtepi8_epi32(raw)); } -template <> -__m128 VectorLoader::to_fp32(__m128i raw) { +template <> __m128 VectorLoader::to_fp32(__m128i raw) { return _mm_cvtepi32_ps(_mm_cvtepu16_epi32(raw)); } -template <> -__m128 VectorLoader::to_fp32(__m128i raw) { +template <> __m128 VectorLoader::to_fp32(__m128i raw) { return _mm_cvtepi32_ps(_mm_cvtepi16_epi32(raw)); } -template <> -__m128 VectorLoader::to_fp32(__m128i raw) { +template <> __m128 VectorLoader::to_fp32(__m128i raw) { return _mm_cvtepi32_ps(raw); } -template <> -__m128 VectorLoader::to_fp32(__m128i raw) { +template <> __m128 VectorLoader::to_fp32(__m128i raw) { #ifdef __F16C__ return _mm_cvtph_ps(raw); #else @@ -1813,8 +1761,7 @@ __m128 VectorLoader::to_fp32(__m128i raw) { return _mm_castsi128_ps(fp32_val); #endif } -template <> -__m128 VectorLoader::to_fp32(__m128i raw) { +template <> __m128 VectorLoader::to_fp32(__m128i raw) { // bfloat16 is essentially fp32 with mantissa truncated from 23 to 7 bits. // can convert with << 16, which we fuse with initial shuffle into epi32 // positions. @@ -1822,8 +1769,7 @@ __m128 VectorLoader::to_fp32(__m128i raw) { -128, 4, 5, -128, -128, 6, 7); return _mm_castsi128_ps(_mm_shuffle_epi8(raw, shuf_hi32)); } -template <> -__m128 VectorLoader::to_fp32(__m128i raw) { +template <> __m128 VectorLoader::to_fp32(__m128i raw) { return _mm_castsi128_ps(raw); } #endif @@ -1882,25 +1828,25 @@ __m128i VectorLoader::extract_right_8b_(const __m128i left) { #ifdef __AVX2__ template -void VectorLoader::load1_1ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m256* left0, __m256* right0) { +void VectorLoader::load1_1ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m256 *left0, __m256 *right0) { __m256i raw = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); *left0 = to_fp32( _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[0]))); *right0 = to_fp32( _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[1]))); } template -void VectorLoader::load1_2ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m256* left0, __m256* left1, __m256* right0, - __m256* right1) { +void VectorLoader::load1_2ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m256 *left0, __m256 *left1, __m256 *right0, + __m256 *right1) { __m256i raw = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); *left0 = to_fp32( _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[0]))); *left1 = to_fp32( @@ -1911,14 +1857,14 @@ void VectorLoader::load1_2ch(const T* lower_ptr, const T* upper_ptr, _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[3]))); } template -void VectorLoader::load1_3ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m256* left0, __m256* left1, __m256* left2, - __m256* right0, __m256* right1, - __m256* right2) { +void VectorLoader::load1_3ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m256 *left0, __m256 *left1, __m256 *left2, + __m256 *right0, __m256 *right1, + __m256 *right2) { __m256i raw = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); *left0 = to_fp32( _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[0]))); *left1 = to_fp32( @@ -1933,14 +1879,14 @@ void VectorLoader::load1_3ch(const T* lower_ptr, const T* upper_ptr, _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[5]))); } template -void VectorLoader::load1_4ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m256* left0, __m256* left1, __m256* left2, - __m256* left3, __m256* right0, __m256* right1, - __m256* right2, __m256* right3) { +void VectorLoader::load1_4ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m256 *left0, __m256 *left1, __m256 *left2, + __m256 *left3, __m256 *right0, __m256 *right1, + __m256 *right2, __m256 *right3) { __m256i raw = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); *left0 = to_fp32( _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[0]))); *left1 = to_fp32( @@ -1959,32 +1905,32 @@ void VectorLoader::load1_4ch(const T* lower_ptr, const T* upper_ptr, _mm256_shuffle_epi8(raw, _mm256_broadcastsi128_si256(shuffle_masks[7]))); } template -void VectorLoader::load2_1ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m256* left0, __m256* right0) { +void VectorLoader::load2_1ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m256 *left0, __m256 *right0) { __m256i raw1 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i raw2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 1)), 1); __m256i mask = _mm256_broadcastsi128_si256(shuffle_masks[0]); *left0 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); *right0 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); } template -void VectorLoader::load2_2ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m256* left0, __m256* left1, __m256* right0, - __m256* right1) { +void VectorLoader::load2_2ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m256 *left0, __m256 *left1, __m256 *right0, + __m256 *right1) { __m256i raw1 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i raw2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 2)), 1); __m256i mask = _mm256_broadcastsi128_si256(shuffle_masks[0]); *left0 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); *right0 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); @@ -1993,18 +1939,18 @@ void VectorLoader::load2_2ch(const T* lower_ptr, const T* upper_ptr, *right1 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); } template -void VectorLoader::load2_3ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m256* left0, __m256* left1, __m256* left2, - __m256* right0, __m256* right1, - __m256* right2) { +void VectorLoader::load2_3ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m256 *left0, __m256 *left1, __m256 *left2, + __m256 *right0, __m256 *right1, + __m256 *right2) { __m256i raw1 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i raw2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 3)), 1); __m256i mask = _mm256_broadcastsi128_si256(shuffle_masks[0]); *left0 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); *right0 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); @@ -2016,18 +1962,18 @@ void VectorLoader::load2_3ch(const T* lower_ptr, const T* upper_ptr, *right2 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); } template -void VectorLoader::load2_4ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m256* left0, __m256* left1, __m256* left2, - __m256* left3, __m256* right0, __m256* right1, - __m256* right2, __m256* right3) { +void VectorLoader::load2_4ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m256 *left0, __m256 *left1, __m256 *left2, + __m256 *left3, __m256 *right0, __m256 *right1, + __m256 *right2, __m256 *right3) { __m256i raw1 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i raw2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 4))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 4)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 4))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 4)), 1); __m256i mask = _mm256_broadcastsi128_si256(shuffle_masks[0]); *left0 = to_fp32(_mm256_shuffle_epi8(raw1, mask)); *right0 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); @@ -2042,12 +1988,12 @@ void VectorLoader::load2_4ch(const T* lower_ptr, const T* upper_ptr, *right3 = to_fp32(_mm256_shuffle_epi8(raw2, mask)); } template -void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load4_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m256* left0, __m256* right0) { + int offset3, __m256 *left0, __m256 *right0) { __m256i l0 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i r0 = extract_right_1ch(l0); __m256i l1, r1; if (offset1 == offset0) { @@ -2056,8 +2002,8 @@ void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, } else { l1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1)), 1); r1 = extract_right_1ch(l1); } __m256i l2, r2; @@ -2067,8 +2013,8 @@ void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, } else { l2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2)), 1); r2 = extract_right_1ch(l2); } __m256i l3, r3; @@ -2078,8 +2024,8 @@ void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, } else { l3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3)), 1); r3 = extract_right_1ch(l3); } pack_1ch(&l0, &l1, &l2, &l3); @@ -2088,13 +2034,13 @@ void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, *right0 = to_fp32(r0); } template -void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load4_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m256* left0, __m256* left1, - __m256* right0, __m256* right1) { + int offset3, __m256 *left0, __m256 *left1, + __m256 *right0, __m256 *right1) { __m256i l0 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i r0 = extract_right_2ch(l0); __m256i l1, r1; if (offset1 == offset0) { @@ -2103,8 +2049,8 @@ void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, } else { l1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1)), 1); r1 = extract_right_2ch(l1); } __m256i l2, r2; @@ -2114,8 +2060,8 @@ void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, } else { l2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2)), 1); r2 = extract_right_2ch(l2); } __m256i l3, r3; @@ -2125,8 +2071,8 @@ void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, } else { l3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3)), 1); r3 = extract_right_2ch(l3); } pack_2ch(&l0, &l1, &l2, &l3); @@ -2137,14 +2083,14 @@ void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, *right1 = to_fp32(r1); } template -void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load4_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m256* left0, __m256* left1, - __m256* left2, __m256* right0, __m256* right1, - __m256* right2) { + int offset3, __m256 *left0, __m256 *left1, + __m256 *left2, __m256 *right0, __m256 *right1, + __m256 *right2) { __m256i l0 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i r0 = extract_right_3ch(l0); __m256i l1, r1; if (offset1 == offset0) { @@ -2153,8 +2099,8 @@ void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, } else { l1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1)), 1); r1 = extract_right_3ch(l1); } __m256i l2, r2; @@ -2164,8 +2110,8 @@ void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, } else { l2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2)), 1); r2 = extract_right_3ch(l2); } __m256i l3, r3; @@ -2175,8 +2121,8 @@ void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, } else { l3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3)), 1); r3 = extract_right_3ch(l3); } pack_3ch(&l0, &l1, &l2, &l3); @@ -2189,15 +2135,15 @@ void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, *right2 = to_fp32(r2); } template -void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load4_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m256* left0, __m256* left1, - __m256* left2, __m256* left3, __m256* right0, - __m256* right1, __m256* right2, - __m256* right3) { + int offset3, __m256 *left0, __m256 *left1, + __m256 *left2, __m256 *left3, __m256 *right0, + __m256 *right1, __m256 *right2, + __m256 *right3) { __m256i l0 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i r0 = extract_right_4ch(l0); __m256i l1, r1; if (offset1 == offset0) { @@ -2206,8 +2152,8 @@ void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, } else { l1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1)), 1); r1 = extract_right_4ch(l1); } __m256i l2, r2; @@ -2217,8 +2163,8 @@ void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, } else { l2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2)), 1); r2 = extract_right_4ch(l2); } __m256i l3, r3; @@ -2228,8 +2174,8 @@ void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, } else { l3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3)), 1); r3 = extract_right_4ch(l3); } *left0 = to_fp32(l0); @@ -2242,16 +2188,16 @@ void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, *right3 = to_fp32(r3); } template -void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load8_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m256* left0, __m256* right0) { + int offset3, __m256 *left0, __m256 *right0) { __m256i l0 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i r0 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 1)), 1); __m256i l1, r1; if (offset1 == offset0) { l1 = l0; @@ -2259,12 +2205,12 @@ void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, } else { l1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1)), 1); r1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1 + 1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1 + 1)), 1); } __m256i l2, r2; if (offset2 == offset1) { @@ -2273,12 +2219,12 @@ void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, } else { l2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2)), 1); r2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2 + 1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2 + 1)), 1); } __m256i l3, r3; if (offset3 == offset2) { @@ -2287,12 +2233,12 @@ void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, } else { l3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3)), 1); r3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3 + 1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3 + 1)), 1); } pack_1ch(&l0, &l1, &l2, &l3); *left0 = to_fp32(l0); @@ -2300,17 +2246,17 @@ void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, *right0 = to_fp32(r0); } template -void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load8_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m256* left0, __m256* left1, - __m256* right0, __m256* right1) { + int offset3, __m256 *left0, __m256 *left1, + __m256 *right0, __m256 *right1) { __m256i l0 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i r0 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 2)), 1); __m256i l1, r1; if (offset1 == offset0) { l1 = l0; @@ -2318,12 +2264,12 @@ void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, } else { l1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1)), 1); r1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1 + 2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1 + 2)), 1); } __m256i l2, r2; if (offset2 == offset1) { @@ -2332,12 +2278,12 @@ void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, } else { l2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2)), 1); r2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2 + 2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2 + 2)), 1); } __m256i l3, r3; if (offset3 == offset2) { @@ -2346,12 +2292,12 @@ void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, } else { l3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3)), 1); r3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3 + 2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3 + 2)), 1); } pack_2ch(&l0, &l1, &l2, &l3); *left0 = to_fp32(l0); @@ -2361,18 +2307,18 @@ void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, *right1 = to_fp32(r1); } template -void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load8_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m256* left0, __m256* left1, - __m256* left2, __m256* right0, __m256* right1, - __m256* right2) { + int offset3, __m256 *left0, __m256 *left1, + __m256 *left2, __m256 *right0, __m256 *right1, + __m256 *right2) { __m256i l0 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i r0 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 3)), 1); __m256i l1, r1; if (offset1 == offset0) { l1 = l0; @@ -2380,12 +2326,12 @@ void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, } else { l1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1)), 1); r1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1 + 3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1 + 3)), 1); } __m256i l2, r2; if (offset2 == offset1) { @@ -2394,12 +2340,12 @@ void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, } else { l2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2)), 1); r2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2 + 3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2 + 3)), 1); } __m256i l3, r3; if (offset3 == offset2) { @@ -2408,12 +2354,12 @@ void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, } else { l3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3)), 1); r3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3 + 3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3 + 3)), 1); } pack_3ch(&l0, &l1, &l2, &l3); *left0 = to_fp32(l0); @@ -2425,19 +2371,19 @@ void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, *right2 = to_fp32(r2); } template -void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load8_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m256* left0, __m256* left1, - __m256* left2, __m256* left3, __m256* right0, - __m256* right1, __m256* right2, - __m256* right3) { + int offset3, __m256 *left0, __m256 *left1, + __m256 *left2, __m256 *left3, __m256 *right0, + __m256 *right1, __m256 *right2, + __m256 *right3) { __m256i l0 = _mm256_insertf128_si256( - _mm256_castsi128_si256(_mm_loadu_si128((__m128i*)(lower_ptr + offset0))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0)), 1); + _mm256_castsi128_si256(_mm_loadu_si128((__m128i *)(lower_ptr + offset0))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0)), 1); __m256i r0 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 4))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 4)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 4))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 4)), 1); __m256i l1, r1; if (offset1 == offset0) { l1 = l0; @@ -2445,12 +2391,12 @@ void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, } else { l1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1)), 1); r1 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 4))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 4)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset1 + 4))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset1 + 4)), 1); } __m256i l2, r2; if (offset2 == offset1) { @@ -2459,12 +2405,12 @@ void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, } else { l2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2)), 1); r2 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 4))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 4)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset2 + 4))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset2 + 4)), 1); } __m256i l3, r3; if (offset3 == offset2) { @@ -2473,12 +2419,12 @@ void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, } else { l3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3)), 1); r3 = _mm256_insertf128_si256( _mm256_castsi128_si256( - _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 4))), - _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 4)), 1); + _mm_loadu_si128((__m128i *)(lower_ptr + offset3 + 4))), + _mm_loadu_si128((__m128i *)(upper_ptr + offset3 + 4)), 1); } *left0 = to_fp32(l0); *left1 = to_fp32(l1); @@ -2491,49 +2437,49 @@ void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, } #else template -void VectorLoader::load1_1ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m128* tl0, __m128* bl0, __m128* tr0, - __m128* br0) { - __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); +void VectorLoader::load1_1ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m128 *tl0, __m128 *bl0, __m128 *tr0, + __m128 *br0) { + __m128i raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); } template -void VectorLoader::load1_2ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m128* tl0, __m128* tl1, __m128* bl0, - __m128* bl1, __m128* tr0, __m128* tr1, - __m128* br0, __m128* br1) { - __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); +void VectorLoader::load1_2ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m128 *tl0, __m128 *tl1, __m128 *bl0, + __m128 *bl1, __m128 *tr0, __m128 *tr1, + __m128 *br0, __m128 *br1) { + __m128i raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); } template -void VectorLoader::load1_3ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m128* tl0, __m128* tl1, __m128* tl2, - __m128* bl0, __m128* bl1, __m128* bl2, - __m128* tr0, __m128* tr1, __m128* tr2, - __m128* br0, __m128* br1, __m128* br2) { - __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); +void VectorLoader::load1_3ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m128 *tl0, __m128 *tl1, __m128 *tl2, + __m128 *bl0, __m128 *bl1, __m128 *bl2, + __m128 *tr0, __m128 *tr1, __m128 *tr2, + __m128 *br0, __m128 *br1, __m128 *br2) { + __m128i raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *tl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[4])); *tr2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[5])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *bl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); @@ -2542,15 +2488,15 @@ void VectorLoader::load1_3ch(const T* lower_ptr, const T* upper_ptr, *br2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[5])); } template -void VectorLoader::load1_4ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m128* tl0, __m128* tl1, __m128* tl2, - __m128* tl3, __m128* bl0, __m128* bl1, - __m128* bl2, __m128* bl3, __m128* tr0, - __m128* tr1, __m128* tr2, __m128* tr3, - __m128* br0, __m128* br1, __m128* br2, - __m128* br3) { - __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); +void VectorLoader::load1_4ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m128 *tl0, __m128 *tl1, __m128 *tl2, + __m128 *tl3, __m128 *bl0, __m128 *bl1, + __m128 *bl2, __m128 *bl3, __m128 *tr0, + __m128 *tr1, __m128 *tr2, __m128 *tr3, + __m128 *br0, __m128 *br1, __m128 *br2, + __m128 *br3) { + __m128i raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *tl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); @@ -2559,7 +2505,7 @@ void VectorLoader::load1_4ch(const T* lower_ptr, const T* upper_ptr, *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[5])); *tr2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[6])); *tr3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[7])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *bl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); @@ -2570,100 +2516,100 @@ void VectorLoader::load1_4ch(const T* lower_ptr, const T* upper_ptr, *br3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[7])); } template -void VectorLoader::load2_1ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m128* tl0, __m128* bl0, __m128* tr0, - __m128* br0) { - __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); +void VectorLoader::load2_1ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m128 *tl0, __m128 *bl0, __m128 *tr0, + __m128 *br0) { + __m128i raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); - raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 1)); + raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 1)); *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 1)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 1)); *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); } template -void VectorLoader::load2_2ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m128* tl0, __m128* tl1, __m128* bl0, - __m128* bl1, __m128* tr0, __m128* tr1, - __m128* br0, __m128* br1) { - __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); +void VectorLoader::load2_2ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m128 *tl0, __m128 *tl1, __m128 *bl0, + __m128 *bl1, __m128 *tr0, __m128 *tr1, + __m128 *br0, __m128 *br1) { + __m128i raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); - raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 2)); + raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 2)); *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 2)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 2)); *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); } template -void VectorLoader::load2_3ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m128* tl0, __m128* tl1, __m128* tl2, - __m128* bl0, __m128* bl1, __m128* bl2, - __m128* tr0, __m128* tr1, __m128* tr2, - __m128* br0, __m128* br1, __m128* br2) { - __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); +void VectorLoader::load2_3ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m128 *tl0, __m128 *tl1, __m128 *tl2, + __m128 *bl0, __m128 *bl1, __m128 *bl2, + __m128 *tr0, __m128 *tr1, __m128 *tr2, + __m128 *br0, __m128 *br1, __m128 *br2) { + __m128i raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *tl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); - raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 3)); + raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 3)); *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *tr2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *bl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 3)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 3)); *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *br2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); } template -void VectorLoader::load2_4ch(const T* lower_ptr, const T* upper_ptr, - int offset0, const __m128i* shuffle_masks, - __m128* tl0, __m128* tl1, __m128* tl2, - __m128* tl3, __m128* bl0, __m128* bl1, - __m128* bl2, __m128* bl3, __m128* tr0, - __m128* tr1, __m128* tr2, __m128* tr3, - __m128* br0, __m128* br1, __m128* br2, - __m128* br3) { - __m128i raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); +void VectorLoader::load2_4ch(const T *lower_ptr, const T *upper_ptr, + int offset0, const __m128i *shuffle_masks, + __m128 *tl0, __m128 *tl1, __m128 *tl2, + __m128 *tl3, __m128 *bl0, __m128 *bl1, + __m128 *bl2, __m128 *bl3, __m128 *tr0, + __m128 *tr1, __m128 *tr2, __m128 *tr3, + __m128 *br0, __m128 *br1, __m128 *br2, + __m128 *br3) { + __m128i raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); *tl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *tl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); *tl3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); - raw = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 4)); + raw = _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 4)); *tr0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *tr1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *tr2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); *tr3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); *bl0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *bl1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *bl2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); *bl3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); - raw = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 4)); + raw = _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 4)); *br0 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[0])); *br1 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[1])); *br2 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[2])); *br3 = to_fp32(_mm_shuffle_epi8(raw, shuffle_masks[3])); } template -void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load4_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m128* tl0, __m128* bl0, - __m128* tr0, __m128* br0) { - __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + int offset3, __m128 *tl0, __m128 *bl0, + __m128 *tr0, __m128 *br0) { + __m128i itl0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); __m128i itr0 = extract_right_1ch(itl0); - __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibl0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); __m128i ibr0 = extract_right_1ch(ibl0); __m128i itl1, itr1; __m128i ibl1, ibr1; @@ -2673,9 +2619,9 @@ void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, ibl1 = ibl0; ibr1 = ibr0; } else { - itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itl1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1)); itr1 = extract_right_1ch(itl1); - ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibl1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1)); ibr1 = extract_right_1ch(ibl1); } __m128i itl2, itr2; @@ -2686,9 +2632,9 @@ void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, ibl2 = ibl1; ibr2 = ibr1; } else { - itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itl2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2)); itr2 = extract_right_1ch(itl2); - ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibl2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2)); ibr2 = extract_right_1ch(ibl2); } __m128i itl3, itr3; @@ -2699,9 +2645,9 @@ void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, ibl3 = ibl2; ibr3 = ibr2; } else { - itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itl3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3)); itr3 = extract_right_1ch(itl3); - ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibl3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3)); ibr3 = extract_right_1ch(ibl3); } pack_1ch(&itl0, &itl1, &itl2, &itl3); @@ -2714,14 +2660,14 @@ void VectorLoader::load4_1ch(const T* lower_ptr, const T* upper_ptr, *br0 = to_fp32(ibr0); } template -void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load4_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m128* tl0, __m128* tl1, - __m128* bl0, __m128* bl1, __m128* tr0, - __m128* tr1, __m128* br0, __m128* br1) { - __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + int offset3, __m128 *tl0, __m128 *tl1, + __m128 *bl0, __m128 *bl1, __m128 *tr0, + __m128 *tr1, __m128 *br0, __m128 *br1) { + __m128i itl0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); __m128i itr0 = extract_right_2ch(itl0); - __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibl0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); __m128i ibr0 = extract_right_2ch(ibl0); __m128i itl1, itr1; __m128i ibl1, ibr1; @@ -2731,9 +2677,9 @@ void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, ibl1 = ibl0; ibr1 = ibr0; } else { - itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itl1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1)); itr1 = extract_right_2ch(itl1); - ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibl1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1)); ibr1 = extract_right_2ch(ibl1); } __m128i itl2, itr2; @@ -2744,9 +2690,9 @@ void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, ibl2 = ibl1; ibr2 = ibr1; } else { - itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itl2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2)); itr2 = extract_right_2ch(itl2); - ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibl2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2)); ibr2 = extract_right_2ch(ibl2); } __m128i itl3, itr3; @@ -2757,9 +2703,9 @@ void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, ibl3 = ibl2; ibr3 = ibr2; } else { - itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itl3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3)); itr3 = extract_right_2ch(itl3); - ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibl3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3)); ibr3 = extract_right_2ch(ibl3); } pack_2ch(&itl0, &itl1, &itl2, &itl3); @@ -2776,16 +2722,16 @@ void VectorLoader::load4_2ch(const T* lower_ptr, const T* upper_ptr, *br1 = to_fp32(ibr1); } template -void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load4_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m128* tl0, __m128* tl1, - __m128* tl2, __m128* bl0, __m128* bl1, - __m128* bl2, __m128* tr0, __m128* tr1, - __m128* tr2, __m128* br0, __m128* br1, - __m128* br2) { - __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + int offset3, __m128 *tl0, __m128 *tl1, + __m128 *tl2, __m128 *bl0, __m128 *bl1, + __m128 *bl2, __m128 *tr0, __m128 *tr1, + __m128 *tr2, __m128 *br0, __m128 *br1, + __m128 *br2) { + __m128i itl0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); __m128i itr0 = extract_right_3ch(itl0); - __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibl0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); __m128i ibr0 = extract_right_3ch(ibl0); __m128i itl1, itr1; __m128i ibl1, ibr1; @@ -2795,9 +2741,9 @@ void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, ibl1 = ibl0; ibr1 = ibr0; } else { - itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itl1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1)); itr1 = extract_right_3ch(itl1); - ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibl1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1)); ibr1 = extract_right_3ch(ibl1); } __m128i itl2, itr2; @@ -2808,9 +2754,9 @@ void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, ibl2 = ibl1; ibr2 = ibr1; } else { - itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itl2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2)); itr2 = extract_right_3ch(itl2); - ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibl2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2)); ibr2 = extract_right_3ch(ibl2); } __m128i itl3, itr3; @@ -2821,9 +2767,9 @@ void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, ibl3 = ibl2; ibr3 = ibr2; } else { - itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itl3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3)); itr3 = extract_right_3ch(itl3); - ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibl3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3)); ibr3 = extract_right_3ch(ibl3); } pack_3ch(&itl0, &itl1, &itl2, &itl3); @@ -2844,17 +2790,17 @@ void VectorLoader::load4_3ch(const T* lower_ptr, const T* upper_ptr, *br2 = to_fp32(ibr2); } template -void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load4_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m128* tl0, __m128* tl1, - __m128* tl2, __m128* tl3, __m128* bl0, - __m128* bl1, __m128* bl2, __m128* bl3, - __m128* tr0, __m128* tr1, __m128* tr2, - __m128* tr3, __m128* br0, __m128* br1, - __m128* br2, __m128* br3) { - __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); + int offset3, __m128 *tl0, __m128 *tl1, + __m128 *tl2, __m128 *tl3, __m128 *bl0, + __m128 *bl1, __m128 *bl2, __m128 *bl3, + __m128 *tr0, __m128 *tr1, __m128 *tr2, + __m128 *tr3, __m128 *br0, __m128 *br1, + __m128 *br2, __m128 *br3) { + __m128i itl0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); __m128i itr0 = extract_right_4ch(itl0); - __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); + __m128i ibl0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); __m128i ibr0 = extract_right_4ch(ibl0); __m128i itl1, itr1; __m128i ibl1, ibr1; @@ -2864,9 +2810,9 @@ void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, ibl1 = ibl0; ibr1 = ibr0; } else { - itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); + itl1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1)); itr1 = extract_right_4ch(itl1); - ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); + ibl1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1)); ibr1 = extract_right_4ch(ibl1); } __m128i itl2, itr2; @@ -2877,9 +2823,9 @@ void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, ibl2 = ibl1; ibr2 = ibr1; } else { - itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); + itl2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2)); itr2 = extract_right_4ch(itl2); - ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); + ibl2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2)); ibr2 = extract_right_4ch(ibl2); } __m128i itl3, itr3; @@ -2890,9 +2836,9 @@ void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, ibl3 = ibl2; ibr3 = ibr2; } else { - itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); + itl3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3)); itr3 = extract_right_4ch(itl3); - ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); + ibl3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3)); ibr3 = extract_right_4ch(ibl3); } *tl0 = to_fp32(itl0); @@ -2913,14 +2859,14 @@ void VectorLoader::load4_4ch(const T* lower_ptr, const T* upper_ptr, *br3 = to_fp32(ibr3); } template -void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load8_1ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m128* tl0, __m128* bl0, - __m128* tr0, __m128* br0) { - __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); - __m128i itr0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 1)); - __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); - __m128i ibr0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 1)); + int offset3, __m128 *tl0, __m128 *bl0, + __m128 *tr0, __m128 *br0) { + __m128i itl0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); + __m128i itr0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 1)); + __m128i ibl0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); + __m128i ibr0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 1)); __m128i itl1, itr1; __m128i ibl1, ibr1; if (offset1 == offset0) { @@ -2929,10 +2875,10 @@ void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, ibl1 = ibl0; ibr1 = ibr0; } else { - itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); - itr1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 1)); - ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); - ibr1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 1)); + itl1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1)); + itr1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1 + 1)); + ibl1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1)); + ibr1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1 + 1)); } __m128i itl2, itr2; __m128i ibl2, ibr2; @@ -2942,10 +2888,10 @@ void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, ibl2 = ibl1; ibr2 = ibr1; } else { - itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); - itr2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 1)); - ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); - ibr2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 1)); + itl2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2)); + itr2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2 + 1)); + ibl2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2)); + ibr2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2 + 1)); } __m128i itl3, itr3; __m128i ibl3, ibr3; @@ -2955,10 +2901,10 @@ void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, ibl3 = ibl2; ibr3 = ibr2; } else { - itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); - itr3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 1)); - ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); - ibr3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 1)); + itl3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3)); + itr3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3 + 1)); + ibl3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3)); + ibr3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3 + 1)); } pack_1ch(&itl0, &itl1, &itl2, &itl3); *tl0 = to_fp32(itl0); @@ -2970,15 +2916,15 @@ void VectorLoader::load8_1ch(const T* lower_ptr, const T* upper_ptr, *br0 = to_fp32(ibr0); } template -void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load8_2ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m128* tl0, __m128* tl1, - __m128* bl0, __m128* bl1, __m128* tr0, - __m128* tr1, __m128* br0, __m128* br1) { - __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); - __m128i itr0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 2)); - __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); - __m128i ibr0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 2)); + int offset3, __m128 *tl0, __m128 *tl1, + __m128 *bl0, __m128 *bl1, __m128 *tr0, + __m128 *tr1, __m128 *br0, __m128 *br1) { + __m128i itl0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); + __m128i itr0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 2)); + __m128i ibl0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); + __m128i ibr0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 2)); __m128i itl1, itr1; __m128i ibl1, ibr1; if (offset1 == offset0) { @@ -2987,10 +2933,10 @@ void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, ibl1 = ibl0; ibr1 = ibr0; } else { - itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); - itr1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 2)); - ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); - ibr1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 2)); + itl1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1)); + itr1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1 + 2)); + ibl1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1)); + ibr1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1 + 2)); } __m128i itl2, itr2; __m128i ibl2, ibr2; @@ -3000,10 +2946,10 @@ void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, ibl2 = ibl1; ibr2 = ibr1; } else { - itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); - itr2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 2)); - ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); - ibr2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 2)); + itl2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2)); + itr2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2 + 2)); + ibl2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2)); + ibr2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2 + 2)); } __m128i itl3, itr3; __m128i ibl3, ibr3; @@ -3013,10 +2959,10 @@ void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, ibl3 = ibl2; ibr3 = ibr2; } else { - itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); - itr3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 2)); - ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); - ibr3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 2)); + itl3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3)); + itr3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3 + 2)); + ibl3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3)); + ibr3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3 + 2)); } pack_2ch(&itl0, &itl1, &itl2, &itl3); *tl0 = to_fp32(itl0); @@ -3032,17 +2978,17 @@ void VectorLoader::load8_2ch(const T* lower_ptr, const T* upper_ptr, *br1 = to_fp32(ibr1); } template -void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load8_3ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m128* tl0, __m128* tl1, - __m128* tl2, __m128* bl0, __m128* bl1, - __m128* bl2, __m128* tr0, __m128* tr1, - __m128* tr2, __m128* br0, __m128* br1, - __m128* br2) { - __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); - __m128i itr0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 3)); - __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); - __m128i ibr0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 3)); + int offset3, __m128 *tl0, __m128 *tl1, + __m128 *tl2, __m128 *bl0, __m128 *bl1, + __m128 *bl2, __m128 *tr0, __m128 *tr1, + __m128 *tr2, __m128 *br0, __m128 *br1, + __m128 *br2) { + __m128i itl0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); + __m128i itr0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 3)); + __m128i ibl0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); + __m128i ibr0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 3)); __m128i itl1, itr1; __m128i ibl1, ibr1; if (offset1 == offset0) { @@ -3051,10 +2997,10 @@ void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, ibl1 = ibl0; ibr1 = ibr0; } else { - itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); - itr1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 3)); - ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); - ibr1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 3)); + itl1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1)); + itr1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1 + 3)); + ibl1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1)); + ibr1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1 + 3)); } __m128i itl2, itr2; __m128i ibl2, ibr2; @@ -3064,10 +3010,10 @@ void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, ibl2 = ibl1; ibr2 = ibr1; } else { - itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); - itr2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 3)); - ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); - ibr2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 3)); + itl2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2)); + itr2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2 + 3)); + ibl2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2)); + ibr2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2 + 3)); } __m128i itl3, itr3; __m128i ibl3, ibr3; @@ -3077,10 +3023,10 @@ void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, ibl3 = ibl2; ibr3 = ibr2; } else { - itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); - itr3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 3)); - ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); - ibr3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 3)); + itl3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3)); + itr3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3 + 3)); + ibl3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3)); + ibr3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3 + 3)); } pack_3ch(&itl0, &itl1, &itl2, &itl3); *tl0 = to_fp32(itl0); @@ -3100,18 +3046,18 @@ void VectorLoader::load8_3ch(const T* lower_ptr, const T* upper_ptr, *br2 = to_fp32(ibr2); } template -void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, +void VectorLoader::load8_4ch(const T *lower_ptr, const T *upper_ptr, int offset0, int offset1, int offset2, - int offset3, __m128* tl0, __m128* tl1, - __m128* tl2, __m128* tl3, __m128* bl0, - __m128* bl1, __m128* bl2, __m128* bl3, - __m128* tr0, __m128* tr1, __m128* tr2, - __m128* tr3, __m128* br0, __m128* br1, - __m128* br2, __m128* br3) { - __m128i itl0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0)); - __m128i itr0 = _mm_loadu_si128((__m128i*)(lower_ptr + offset0 + 4)); - __m128i ibl0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0)); - __m128i ibr0 = _mm_loadu_si128((__m128i*)(upper_ptr + offset0 + 4)); + int offset3, __m128 *tl0, __m128 *tl1, + __m128 *tl2, __m128 *tl3, __m128 *bl0, + __m128 *bl1, __m128 *bl2, __m128 *bl3, + __m128 *tr0, __m128 *tr1, __m128 *tr2, + __m128 *tr3, __m128 *br0, __m128 *br1, + __m128 *br2, __m128 *br3) { + __m128i itl0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0)); + __m128i itr0 = _mm_loadu_si128((__m128i *)(lower_ptr + offset0 + 4)); + __m128i ibl0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0)); + __m128i ibr0 = _mm_loadu_si128((__m128i *)(upper_ptr + offset0 + 4)); __m128i itl1, itr1; __m128i ibl1, ibr1; if (offset1 == offset0) { @@ -3120,10 +3066,10 @@ void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, ibl1 = ibl0; ibr1 = ibr0; } else { - itl1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1)); - itr1 = _mm_loadu_si128((__m128i*)(lower_ptr + offset1 + 4)); - ibl1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1)); - ibr1 = _mm_loadu_si128((__m128i*)(upper_ptr + offset1 + 4)); + itl1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1)); + itr1 = _mm_loadu_si128((__m128i *)(lower_ptr + offset1 + 4)); + ibl1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1)); + ibr1 = _mm_loadu_si128((__m128i *)(upper_ptr + offset1 + 4)); } __m128i itl2, itr2; __m128i ibl2, ibr2; @@ -3133,10 +3079,10 @@ void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, ibl2 = ibl1; ibr2 = ibr1; } else { - itl2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2)); - itr2 = _mm_loadu_si128((__m128i*)(lower_ptr + offset2 + 4)); - ibl2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2)); - ibr2 = _mm_loadu_si128((__m128i*)(upper_ptr + offset2 + 4)); + itl2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2)); + itr2 = _mm_loadu_si128((__m128i *)(lower_ptr + offset2 + 4)); + ibl2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2)); + ibr2 = _mm_loadu_si128((__m128i *)(upper_ptr + offset2 + 4)); } __m128i itl3, itr3; __m128i ibl3, ibr3; @@ -3146,10 +3092,10 @@ void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, ibl3 = ibl2; ibr3 = ibr2; } else { - itl3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3)); - itr3 = _mm_loadu_si128((__m128i*)(lower_ptr + offset3 + 4)); - ibl3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3)); - ibr3 = _mm_loadu_si128((__m128i*)(upper_ptr + offset3 + 4)); + itl3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3)); + itr3 = _mm_loadu_si128((__m128i *)(lower_ptr + offset3 + 4)); + ibl3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3)); + ibr3 = _mm_loadu_si128((__m128i *)(upper_ptr + offset3 + 4)); } *tl0 = to_fp32(itl0); *tl1 = to_fp32(itl1); @@ -3177,9 +3123,8 @@ void VectorLoader::load8_4ch(const T* lower_ptr, const T* upper_ptr, // bfloat16 or float. // -template -class VectorWriter { - public: +template class VectorWriter { +public: // convert 4 fp32 words to type U with. // this function calls clip. // resulting words are packed. @@ -3189,89 +3134,89 @@ class VectorWriter { // converts from fp32 to U by calling method from_fp32(...) // writes 4 pixels with 1 channel to destination. - void write_1ch(U* destination, __m128* vec); + void write_1ch(U *destination, __m128 *vec); // converts from fp32 to U by calling method from_fp32(...) // writes 4 pixels with 1 channel to destination. - void write_2ch(U* destination, __m128* vec); + void write_2ch(U *destination, __m128 *vec); // converts from fp32 to U by calling method from_fp32(...) // writes 4 pixels with 1 channel to destination. - void write_3ch(U* destination, __m128* vec); + void write_3ch(U *destination, __m128 *vec); // converts from fp32 to U by calling method from_fp32(...) // writes 4 pixels with 1 channel to destination. - void write_4ch(U* destination, __m128* vec); + void write_4ch(U *destination, __m128 *vec); - private: +private: // clip 4 fp32 words to prevent overflow when converting to type U. __m128 clip_(__m128 vec) { // default is to do nothing, since the packing intrinsics include clipping. return vec; } - void write_1b_1ch(U* destination, __m128* vec) { + void write_1b_1ch(U *destination, __m128 *vec) { __m128i ivec = from_fp32(vec[0]); - _mm_store_ss((float*)(destination), _mm_castsi128_ps(ivec)); + _mm_store_ss((float *)(destination), _mm_castsi128_ps(ivec)); } - void write_2b_1ch(U* destination, __m128* vec) { + void write_2b_1ch(U *destination, __m128 *vec) { __m128i ivec = from_fp32(vec[0]); - _mm_store_sd((double*)(destination), _mm_castsi128_pd(ivec)); + _mm_store_sd((double *)(destination), _mm_castsi128_pd(ivec)); } - void write_4b_1ch(U* destination, __m128* vec) { + void write_4b_1ch(U *destination, __m128 *vec) { __m128i ivec = from_fp32(vec[0]); - _mm_storeu_si128((__m128i*)(destination), ivec); + _mm_storeu_si128((__m128i *)(destination), ivec); } - void write_1b_2ch(U* destination, __m128* vec) { + void write_1b_2ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); __m128i mask = _mm_setr_epi32(-1, 0, 0, 0); ivec1 = _mm_or_si128(_mm_and_si128(mask, ivec1), _mm_slli_si128(_mm_and_si128(mask, ivec2), 4)); - _mm_store_sd((double*)(destination), _mm_castsi128_pd(ivec1)); + _mm_store_sd((double *)(destination), _mm_castsi128_pd(ivec1)); } - void write_2b_2ch(U* destination, __m128* vec) { + void write_2b_2ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); __m128i mask = _mm_setr_epi32(-1, -1, 0, 0); ivec1 = _mm_or_si128(_mm_and_si128(mask, ivec1), _mm_slli_si128(_mm_and_si128(mask, ivec2), 8)); - _mm_storeu_si128((__m128i*)(destination), ivec1); + _mm_storeu_si128((__m128i *)(destination), ivec1); } - void write_4b_2ch(U* destination, __m128* vec) { + void write_4b_2ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); - _mm_storeu_si128((__m128i*)(destination), ivec1); - _mm_storeu_si128((__m128i*)(destination + 4), ivec2); + _mm_storeu_si128((__m128i *)(destination), ivec1); + _mm_storeu_si128((__m128i *)(destination + 4), ivec2); } - void write_1b_3ch(U* destination, __m128* vec) { + void write_1b_3ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); __m128i mask = _mm_setr_epi32(-1, 0, 0, 0); ivec1 = _mm_or_si128(_mm_and_si128(mask, ivec1), _mm_slli_si128(_mm_and_si128(mask, ivec2), 4)); - _mm_store_sd((double*)(destination), _mm_castsi128_pd(ivec1)); + _mm_store_sd((double *)(destination), _mm_castsi128_pd(ivec1)); __m128i ivec3 = from_fp32(vec[2]); - _mm_store_ss((float*)(destination + 8), _mm_castsi128_ps(ivec3)); + _mm_store_ss((float *)(destination + 8), _mm_castsi128_ps(ivec3)); } - void write_2b_3ch(U* destination, __m128* vec) { + void write_2b_3ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); __m128i mask = _mm_setr_epi32(-1, -1, 0, 0); ivec1 = _mm_or_si128(_mm_and_si128(mask, ivec1), _mm_slli_si128(_mm_and_si128(mask, ivec2), 8)); - _mm_storeu_si128((__m128i*)(destination), ivec1); + _mm_storeu_si128((__m128i *)(destination), ivec1); __m128i ivec3 = from_fp32(vec[2]); - _mm_store_sd((double*)(destination + 8), _mm_castsi128_pd(ivec3)); + _mm_store_sd((double *)(destination + 8), _mm_castsi128_pd(ivec3)); } - void write_4b_3ch(U* destination, __m128* vec) { + void write_4b_3ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); __m128i ivec3 = from_fp32(vec[2]); - _mm_storeu_si128((__m128i*)(destination), ivec1); - _mm_storeu_si128((__m128i*)(destination + 4), ivec2); - _mm_storeu_si128((__m128i*)(destination + 8), ivec3); + _mm_storeu_si128((__m128i *)(destination), ivec1); + _mm_storeu_si128((__m128i *)(destination + 4), ivec2); + _mm_storeu_si128((__m128i *)(destination + 8), ivec3); } - void write_1b_4ch(U* destination, __m128* vec) { + void write_1b_4ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); __m128i ivec3 = from_fp32(vec[2]); @@ -3281,9 +3226,9 @@ class VectorWriter { ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec2), 4)); ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec3), 8)); ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec4), 12)); - _mm_storeu_si128((__m128i*)(destination), ivec); + _mm_storeu_si128((__m128i *)(destination), ivec); } - void write_2b_4ch(U* destination, __m128* vec) { + void write_2b_4ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); __m128i ivec3 = from_fp32(vec[2]); @@ -3291,25 +3236,24 @@ class VectorWriter { __m128i mask = _mm_setr_epi32(-1, -1, 0, 0); __m128i ivec = _mm_and_si128(mask, ivec1); ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec2), 8)); - _mm_storeu_si128((__m128i*)(destination), ivec); + _mm_storeu_si128((__m128i *)(destination), ivec); ivec = _mm_and_si128(mask, ivec3); ivec = _mm_or_si128(ivec, _mm_slli_si128(_mm_and_si128(mask, ivec4), 8)); - _mm_storeu_si128((__m128i*)(destination + 8), ivec); + _mm_storeu_si128((__m128i *)(destination + 8), ivec); } - void write_4b_4ch(U* destination, __m128* vec) { + void write_4b_4ch(U *destination, __m128 *vec) { __m128i ivec1 = from_fp32(vec[0]); __m128i ivec2 = from_fp32(vec[1]); __m128i ivec3 = from_fp32(vec[2]); __m128i ivec4 = from_fp32(vec[3]); - _mm_storeu_si128((__m128i*)(destination), ivec1); - _mm_storeu_si128((__m128i*)(destination + 4), ivec2); - _mm_storeu_si128((__m128i*)(destination + 8), ivec3); - _mm_storeu_si128((__m128i*)(destination + 12), ivec4); + _mm_storeu_si128((__m128i *)(destination), ivec1); + _mm_storeu_si128((__m128i *)(destination + 4), ivec2); + _mm_storeu_si128((__m128i *)(destination + 8), ivec3); + _mm_storeu_si128((__m128i *)(destination + 12), ivec4); } }; -template <> -__m128 VectorWriter::clip_(__m128 vec) { +template <> __m128 VectorWriter::clip_(__m128 vec) { // clip against low limit, -2147483648. // we round up to nearest number that can be represented as float. __m128 lt_val = _mm_set1_ps(-2147483520.0f); @@ -3322,8 +3266,7 @@ __m128 VectorWriter::clip_(__m128 vec) { vec = _mm_or_ps(_mm_andnot_ps(gt_mask, vec), _mm_and_ps(gt_mask, gt_val)); return vec; } -template <> -__m128 VectorWriter::clip_(__m128 vec) { +template <> __m128 VectorWriter::clip_(__m128 vec) { // clip against low limit, -65504.0f; __m128 lt_val = _mm_set1_ps(-65504.0f); __m128 lt_mask = _mm_cmplt_ps(vec, lt_val); @@ -3335,34 +3278,28 @@ __m128 VectorWriter::clip_(__m128 vec) { return vec; } -template <> -__m128i VectorWriter::from_fp32(__m128 vec) { +template <> __m128i VectorWriter::from_fp32(__m128 vec) { __m128i ivec = _mm_cvttps_epi32(vec); ivec = _mm_packs_epi32(ivec, ivec); return _mm_packus_epi16(ivec, ivec); } -template <> -__m128i VectorWriter::from_fp32(__m128 vec) { +template <> __m128i VectorWriter::from_fp32(__m128 vec) { __m128i ivec = _mm_cvttps_epi32(vec); ivec = _mm_packs_epi32(ivec, ivec); return _mm_packs_epi16(ivec, ivec); } -template <> -__m128i VectorWriter::from_fp32(__m128 vec) { +template <> __m128i VectorWriter::from_fp32(__m128 vec) { __m128i ivec = _mm_cvttps_epi32(vec); return _mm_packus_epi32(ivec, ivec); } -template <> -__m128i VectorWriter::from_fp32(__m128 vec) { +template <> __m128i VectorWriter::from_fp32(__m128 vec) { __m128i ivec = _mm_cvttps_epi32(vec); return _mm_packs_epi32(ivec, ivec); } -template <> -__m128i VectorWriter::from_fp32(__m128 vec) { +template <> __m128i VectorWriter::from_fp32(__m128 vec) { return _mm_cvttps_epi32(clip_(vec)); } -template <> -__m128i VectorWriter::from_fp32(__m128 vec) { +template <> __m128i VectorWriter::from_fp32(__m128 vec) { #ifdef __F16C__ return _mm_cvtps_ph(vec, _MM_FROUND_TO_ZERO); #else @@ -3426,8 +3363,7 @@ __m128i VectorWriter::from_fp32(__m128 vec) { return number; #endif } -template <> -__m128i VectorWriter::from_fp32(__m128 vec) { +template <> __m128i VectorWriter::from_fp32(__m128 vec) { // casting from float to bfloat16 simply means >> 16 // we do this with a shuffle that also moves everything to lower portion of // sse vector word @@ -3435,181 +3371,166 @@ __m128i VectorWriter::from_fp32(__m128 vec) { -128, -128, -128, -128, -128, -128); return _mm_shuffle_epi8(_mm_castps_si128(vec), shuf_from_hi32); } -template <> -__m128i VectorWriter::from_fp32(__m128 vec) { +template <> __m128i VectorWriter::from_fp32(__m128 vec) { // nothing to do in this case return _mm_castps_si128(vec); } template <> -void VectorWriter::write_1ch(uint8* destination, __m128* vec) { +void VectorWriter::write_1ch(uint8 *destination, __m128 *vec) { write_1b_1ch(destination, vec); } -template <> -void VectorWriter::write_1ch(int8* destination, __m128* vec) { +template <> void VectorWriter::write_1ch(int8 *destination, __m128 *vec) { write_1b_1ch(destination, vec); } template <> -void VectorWriter::write_1ch(uint16* destination, __m128* vec) { +void VectorWriter::write_1ch(uint16 *destination, __m128 *vec) { write_2b_1ch(destination, vec); } template <> -void VectorWriter::write_1ch(int16* destination, __m128* vec) { +void VectorWriter::write_1ch(int16 *destination, __m128 *vec) { write_2b_1ch(destination, vec); } template <> -void VectorWriter::write_1ch(int32* destination, __m128* vec) { +void VectorWriter::write_1ch(int32 *destination, __m128 *vec) { write_4b_1ch(destination, vec); } template <> -void VectorWriter::write_1ch(Eigen::half* destination, - __m128* vec) { +void VectorWriter::write_1ch(Eigen::half *destination, + __m128 *vec) { write_2b_1ch(destination, vec); } template <> -void VectorWriter::write_1ch(bfloat16* destination, __m128* vec) { +void VectorWriter::write_1ch(bfloat16 *destination, __m128 *vec) { write_2b_1ch(destination, vec); } template <> -void VectorWriter::write_1ch(float* destination, __m128* vec) { - _mm_storeu_si128((__m128i*)(destination), _mm_castps_si128(vec[0])); +void VectorWriter::write_1ch(float *destination, __m128 *vec) { + _mm_storeu_si128((__m128i *)(destination), _mm_castps_si128(vec[0])); } template <> -void VectorWriter::write_2ch(uint8* destination, __m128* vec) { +void VectorWriter::write_2ch(uint8 *destination, __m128 *vec) { write_1b_2ch(destination, vec); } -template <> -void VectorWriter::write_2ch(int8* destination, __m128* vec) { +template <> void VectorWriter::write_2ch(int8 *destination, __m128 *vec) { write_1b_2ch(destination, vec); } template <> -void VectorWriter::write_2ch(uint16* destination, __m128* vec) { +void VectorWriter::write_2ch(uint16 *destination, __m128 *vec) { write_2b_2ch(destination, vec); } template <> -void VectorWriter::write_2ch(int16* destination, __m128* vec) { +void VectorWriter::write_2ch(int16 *destination, __m128 *vec) { write_2b_2ch(destination, vec); } template <> -void VectorWriter::write_2ch(int32* destination, __m128* vec) { +void VectorWriter::write_2ch(int32 *destination, __m128 *vec) { write_4b_2ch(destination, vec); } template <> -void VectorWriter::write_2ch(Eigen::half* destination, - __m128* vec) { +void VectorWriter::write_2ch(Eigen::half *destination, + __m128 *vec) { write_2b_2ch(destination, vec); } template <> -void VectorWriter::write_2ch(bfloat16* destination, __m128* vec) { +void VectorWriter::write_2ch(bfloat16 *destination, __m128 *vec) { write_2b_2ch(destination, vec); } template <> -void VectorWriter::write_2ch(float* destination, __m128* vec) { - _mm_storeu_si128((__m128i*)(destination), _mm_castps_si128(vec[0])); - _mm_storeu_si128((__m128i*)(destination + 4), _mm_castps_si128(vec[1])); +void VectorWriter::write_2ch(float *destination, __m128 *vec) { + _mm_storeu_si128((__m128i *)(destination), _mm_castps_si128(vec[0])); + _mm_storeu_si128((__m128i *)(destination + 4), _mm_castps_si128(vec[1])); } template <> -void VectorWriter::write_3ch(uint8* destination, __m128* vec) { +void VectorWriter::write_3ch(uint8 *destination, __m128 *vec) { write_1b_3ch(destination, vec); } -template <> -void VectorWriter::write_3ch(int8* destination, __m128* vec) { +template <> void VectorWriter::write_3ch(int8 *destination, __m128 *vec) { write_1b_3ch(destination, vec); } template <> -void VectorWriter::write_3ch(uint16* destination, __m128* vec) { +void VectorWriter::write_3ch(uint16 *destination, __m128 *vec) { write_2b_3ch(destination, vec); } template <> -void VectorWriter::write_3ch(int16* destination, __m128* vec) { +void VectorWriter::write_3ch(int16 *destination, __m128 *vec) { write_2b_3ch(destination, vec); } template <> -void VectorWriter::write_3ch(int32* destination, __m128* vec) { +void VectorWriter::write_3ch(int32 *destination, __m128 *vec) { write_4b_3ch(destination, vec); } template <> -void VectorWriter::write_3ch(Eigen::half* destination, - __m128* vec) { +void VectorWriter::write_3ch(Eigen::half *destination, + __m128 *vec) { write_2b_3ch(destination, vec); } template <> -void VectorWriter::write_3ch(bfloat16* destination, __m128* vec) { +void VectorWriter::write_3ch(bfloat16 *destination, __m128 *vec) { write_2b_3ch(destination, vec); } template <> -void VectorWriter::write_3ch(float* destination, __m128* vec) { - _mm_storeu_si128((__m128i*)(destination), _mm_castps_si128(vec[0])); - _mm_storeu_si128((__m128i*)(destination + 4), _mm_castps_si128(vec[1])); - _mm_storeu_si128((__m128i*)(destination + 8), _mm_castps_si128(vec[2])); +void VectorWriter::write_3ch(float *destination, __m128 *vec) { + _mm_storeu_si128((__m128i *)(destination), _mm_castps_si128(vec[0])); + _mm_storeu_si128((__m128i *)(destination + 4), _mm_castps_si128(vec[1])); + _mm_storeu_si128((__m128i *)(destination + 8), _mm_castps_si128(vec[2])); } template <> -void VectorWriter::write_4ch(uint8* destination, __m128* vec) { +void VectorWriter::write_4ch(uint8 *destination, __m128 *vec) { write_1b_4ch(destination, vec); } -template <> -void VectorWriter::write_4ch(int8* destination, __m128* vec) { +template <> void VectorWriter::write_4ch(int8 *destination, __m128 *vec) { write_1b_4ch(destination, vec); } template <> -void VectorWriter::write_4ch(uint16* destination, __m128* vec) { +void VectorWriter::write_4ch(uint16 *destination, __m128 *vec) { write_2b_4ch(destination, vec); } template <> -void VectorWriter::write_4ch(int16* destination, __m128* vec) { +void VectorWriter::write_4ch(int16 *destination, __m128 *vec) { write_2b_4ch(destination, vec); } template <> -void VectorWriter::write_4ch(int32* destination, __m128* vec) { +void VectorWriter::write_4ch(int32 *destination, __m128 *vec) { write_4b_4ch(destination, vec); } template <> -void VectorWriter::write_4ch(Eigen::half* destination, - __m128* vec) { +void VectorWriter::write_4ch(Eigen::half *destination, + __m128 *vec) { write_2b_4ch(destination, vec); } template <> -void VectorWriter::write_4ch(bfloat16* destination, __m128* vec) { +void VectorWriter::write_4ch(bfloat16 *destination, __m128 *vec) { write_2b_4ch(destination, vec); } template <> -void VectorWriter::write_4ch(float* destination, __m128* vec) { - _mm_storeu_si128((__m128i*)(destination), _mm_castps_si128(vec[0])); - _mm_storeu_si128((__m128i*)(destination + 4), _mm_castps_si128(vec[1])); - _mm_storeu_si128((__m128i*)(destination + 8), _mm_castps_si128(vec[2])); - _mm_storeu_si128((__m128i*)(destination + 12), _mm_castps_si128(vec[3])); +void VectorWriter::write_4ch(float *destination, __m128 *vec) { + _mm_storeu_si128((__m128i *)(destination), _mm_castps_si128(vec[0])); + _mm_storeu_si128((__m128i *)(destination + 4), _mm_castps_si128(vec[1])); + _mm_storeu_si128((__m128i *)(destination + 8), _mm_castps_si128(vec[2])); + _mm_storeu_si128((__m128i *)(destination + 12), _mm_castps_si128(vec[3])); } template class CropResizeCastImage : public VectorLoader, public VectorWriter { - public: +public: CropResizeCastImage(const int in_height, const int in_width, const int out_height, const int out_width, const int channels, const int min_ix, const int max_ix, - const CachedInterpolation* xs, const int min_iy, - const int max_iy, const CachedInterpolation* ys, + const CachedInterpolation *xs, const int min_iy, + const int max_iy, const CachedInterpolation *ys, const float extrapolated_value, const bool flip_x, const bool flip_y, const bool verbose = false, const int allowed_load_groups = 15) - : verbose_(verbose), - allowed_load_groups_(allowed_load_groups), - in_height_(in_height), - in_width_(in_width), - out_height_(out_height), - out_width_(out_width), - channels_(channels), - min_ix_(min_ix), - max_ix_(max_ix), - min_iy_(min_iy), - max_iy_(max_iy), - ys_(ys), - extrapolated_value_(extrapolated_value), - flip_x_(flip_x), - flip_y_(flip_y), - in_row_size_(in_width * channels), + : verbose_(verbose), allowed_load_groups_(allowed_load_groups), + in_height_(in_height), in_width_(in_width), out_height_(out_height), + out_width_(out_width), channels_(channels), min_ix_(min_ix), + max_ix_(max_ix), min_iy_(min_iy), max_iy_(max_iy), ys_(ys), + extrapolated_value_(extrapolated_value), flip_x_(flip_x), + flip_y_(flip_y), in_row_size_(in_width * channels), in_row_size_bytes_(in_width * channels * sizeof(T)), out_row_size_(out_width * channels), x0_(flip_x ? out_width - 1 - max_ix : min_ix), @@ -3622,21 +3543,21 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { // xs[].lower == xs[].upper AND xs[].lerp == 1 xs_ = new CachedInterpolation[max_ix_ - min_ix_ + 1]; for (int i = min_ix_; i <= max_ix_; ++i) { - int ix = i - min_ix_; - int xs_lower = xs[ix].lower / channels_; - int xs_upper = xs[ix].upper / channels_; - if (xs_lower == xs_upper) { - if (xs[ix].lerp == 0.0f && xs_lower + 1 < in_width) { - // upper weight is zero - xs_upper = xs_lower + 1; - } else if (xs[ix].lerp == 1.0f && xs_upper - 1 >= 0) { - // lower weight is zero - xs_lower = xs_upper - 1; - } - } - xs_[ix].lower = xs_lower * channels_; - xs_[ix].upper = xs_upper * channels_; - xs_[ix].lerp = xs[ix].lerp; + int ix = i - min_ix_; + int xs_lower = xs[ix].lower / channels_; + int xs_upper = xs[ix].upper / channels_; + if (xs_lower == xs_upper) { + if (xs[ix].lerp == 0.0f && xs_lower + 1 < in_width) { + // upper weight is zero + xs_upper = xs_lower + 1; + } else if (xs[ix].lerp == 1.0f && xs_upper - 1 >= 0) { + // lower weight is zero + xs_lower = xs_upper - 1; + } + } + xs_[ix].lower = xs_lower * channels_; + xs_[ix].upper = xs_upper * channels_; + xs_[ix].lerp = xs[ix].lerp; } _u_min_val = std::numeric_limits::min(); _u_max_val = std::numeric_limits::max(); @@ -3665,25 +3586,40 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { } } ~CropResizeCastImage() { - if (general_x_ != NULL) delete[] general_x_; - if (load1_x_ != NULL) delete[] load1_x_; - if (load2_x_ != NULL) delete[] load2_x_; - if (load4_x_ != NULL) delete[] load4_x_; - if (load8_x_ != NULL) delete[] load8_x_; - if (load1_offsets_ != NULL) delete[] load1_offsets_; - if (load2_offsets_ != NULL) delete[] load2_offsets_; - if (load4_offsets_ != NULL) delete[] load4_offsets_; - if (load8_offsets_ != NULL) delete[] load8_offsets_; - if (load1_shuffle_masks_ != NULL) delete[] load1_shuffle_masks_; - if (load2_shuffle_masks_ != NULL) delete[] load2_shuffle_masks_; - if (load1_mmxs_lerp_ != NULL) delete[] load1_mmxs_lerp_; - if (load2_mmxs_lerp_ != NULL) delete[] load2_mmxs_lerp_; - if (load4_mmxs_lerp_ != NULL) delete[] load4_mmxs_lerp_; - if (load8_mmxs_lerp_ != NULL) delete[] load8_mmxs_lerp_; + if (general_x_ != NULL) + delete[] general_x_; + if (load1_x_ != NULL) + delete[] load1_x_; + if (load2_x_ != NULL) + delete[] load2_x_; + if (load4_x_ != NULL) + delete[] load4_x_; + if (load8_x_ != NULL) + delete[] load8_x_; + if (load1_offsets_ != NULL) + delete[] load1_offsets_; + if (load2_offsets_ != NULL) + delete[] load2_offsets_; + if (load4_offsets_ != NULL) + delete[] load4_offsets_; + if (load8_offsets_ != NULL) + delete[] load8_offsets_; + if (load1_shuffle_masks_ != NULL) + delete[] load1_shuffle_masks_; + if (load2_shuffle_masks_ != NULL) + delete[] load2_shuffle_masks_; + if (load1_mmxs_lerp_ != NULL) + delete[] load1_mmxs_lerp_; + if (load2_mmxs_lerp_ != NULL) + delete[] load2_mmxs_lerp_; + if (load4_mmxs_lerp_ != NULL) + delete[] load4_mmxs_lerp_; + if (load8_mmxs_lerp_ != NULL) + delete[] load8_mmxs_lerp_; delete[] xs_; } - private: +private: // constructor arguments const bool verbose_; // this value is meant for unit testing. @@ -3697,8 +3633,8 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { const int in_height_, in_width_, out_height_, out_width_; const int channels_; const int min_ix_, max_ix_, min_iy_, max_iy_; - const CachedInterpolation* ys_; - CachedInterpolation* xs_; + const CachedInterpolation *ys_; + CachedInterpolation *xs_; const float extrapolated_value_; const bool flip_x_, flip_y_; // computed arguments @@ -3709,40 +3645,40 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { const int y0_, y1_; // helper methods - void ResizeRow_load1_1ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load2_1ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load4_1ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load8_1ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load1_2ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load2_2ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load4_2ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load8_2ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load1_3ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load2_3ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load4_3ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load8_3ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load1_4ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load2_4ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load4_4ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_load8_4ch_(const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); - void ResizeRow_general_(const float ys_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr); + void ResizeRow_load1_1ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load2_1ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load4_1ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load8_1ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load1_2ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load2_2ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load4_2ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load8_2ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load1_3ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load2_3ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load4_3ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load8_3ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load1_4ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load2_4ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load4_4ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_load8_4ch_(const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); + void ResizeRow_general_(const float ys_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr); // configuration parameters int num_general_, num_load1_, num_load2_, num_load4_, num_load8_; @@ -3756,17 +3692,17 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { // configuration methods void Configure_(); int DetermineLoadGroup_(const int x); - bool ComputeXIndexRange_(const int x, int* min_xidx, int* max_xidx); - bool Load1_ok_( - const int min_xidx, - const int max_xidx); // xs - pointer to first xs for this load group - bool Load2_ok_( - const int min_xidx, - const int max_xidx); // xs - pointer to first xs for this load group + bool ComputeXIndexRange_(const int x, int *min_xidx, int *max_xidx); + bool + Load1_ok_(const int min_xidx, + const int max_xidx); // xs - pointer to first xs for this load group + bool + Load2_ok_(const int min_xidx, + const int max_xidx); // xs - pointer to first xs for this load group bool Load4_ok_(const int min_xidx, const int max_xidx); bool Load8_ok_(const int min_xidx, const int max_xidx); - public: +public: // // public client methods // @@ -3776,34 +3712,36 @@ class CropResizeCastImage : public VectorLoader, public VectorWriter { static bool clip_necessary(); // resize image - void Resize(const T* input_image, U* output_image); + void Resize(const T *input_image, U *output_image); }; template -void CropResizeCastImage::Resize(const T* input_image, U* output_image) { +void CropResizeCastImage::Resize(const T *input_image, U *output_image) { // U uEx = cast_to(extrapolated_value_, _f_min_val, _f_max_val, _u_min_val, _u_max_val); // extrapolate top if (min_iy_ > 0) { - U* p = flip_y_ ? output_image + out_row_size_ * (out_height_ - min_iy_) + U *p = flip_y_ ? output_image + out_row_size_ * (out_height_ - min_iy_) : output_image; int nn = out_row_size_ * min_iy_; - for (int i = 0; i < nn; ++i) p[i] = uEx; + for (int i = 0; i < nn; ++i) + p[i] = uEx; } // extrapolate bottom if (max_iy_ < out_height_ - 1) { - U* p = + U *p = flip_y_ ? output_image : output_image + out_row_size_ * (max_iy_ + 1); int nn = out_row_size_ * (out_height_ - 1 - max_iy_); - for (int i = 0; i < nn; ++i) p[i] = uEx; + for (int i = 0; i < nn; ++i) + p[i] = uEx; } // extrapolate left if (min_ix_ > 0) { for (int iy = min_iy_; iy <= max_iy_; ++iy) { int xx0 = flip_x_ ? (out_width_ - min_ix_) * channels_ : 0; int nxx = min_ix_ * channels_; - U* p = output_image + xx0 + + U *p = output_image + xx0 + out_row_size_ * (flip_y_ ? out_height_ - 1 - iy : iy); for (int ix = 0; ix < nxx; ++ix) { p[ix] = uEx; @@ -3815,7 +3753,7 @@ void CropResizeCastImage::Resize(const T* input_image, U* output_image) { for (int iy = min_iy_; iy <= max_iy_; ++iy) { int xx0 = flip_x_ ? 0 : (max_ix_ + 1) * channels_; int nxx = (out_width_ - 1 - max_ix_) * channels_; - U* p = output_image + xx0 + + U *p = output_image + xx0 + out_row_size_ * (flip_y_ ? out_height_ - 1 - iy : iy); for (int ix = 0; ix < nxx; ++ix) { p[ix] = uEx; @@ -3829,163 +3767,163 @@ void CropResizeCastImage::Resize(const T* input_image, U* output_image) { const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; const float yA_lerp = ys_[iyA].lerp; const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); - const T* ysA_input_lower_ptr = - input_image + ys_[iyA].lower * in_width_ * channels_; - const T* ysA_input_upper_ptr = - input_image + ys_[iyA].upper * in_width_ * channels_; - U* ysA_output_ptr = output_image + y * out_width_ * channels_; + const T *ysA_input_lower_ptr = + input_image + ys_[iyA].lower * in_width_ * channels_; + const T *ysA_input_upper_ptr = + input_image + ys_[iyA].upper * in_width_ * channels_; + U *ysA_output_ptr = output_image + y * out_width_ * channels_; const int iyB = - flip_y_ ? out_height_ - 1 - min_iy_ - (y + 1) : (y + 1) - min_iy_; + flip_y_ ? out_height_ - 1 - min_iy_ - (y + 1) : (y + 1) - min_iy_; const float yB_lerp = ys_[iyB].lerp; const __m128 ysB_lerp = _mm_set1_ps(yB_lerp); - const T* ysB_input_lower_ptr = - input_image + ys_[iyB].lower * in_width_ * channels_; - const T* ysB_input_upper_ptr = - input_image + ys_[iyB].upper * in_width_ * channels_; - U* ysB_output_ptr = output_image + (y + 1) * out_width_ * channels_; + const T *ysB_input_lower_ptr = + input_image + ys_[iyB].lower * in_width_ * channels_; + const T *ysB_input_upper_ptr = + input_image + ys_[iyB].upper * in_width_ * channels_; + U *ysB_output_ptr = output_image + (y + 1) * out_width_ * channels_; if (channels_ == 1) { - this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load1_1ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_1ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_1ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_1ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_1ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); } else if (channels_ == 2) { - this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load1_2ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_2ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_2ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_2ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_2ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); } else if (channels_ == 3) { - this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load1_3ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_3ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_3ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_3ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_3ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); } else if (channels_ == 4) { - this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load1_4ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_4ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_4ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_4ch_(ysB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, - ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_4ch_(ysB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yB_lerp, ysB_input_lower_ptr, + ysB_input_upper_ptr, ysB_output_ptr); } else { - assert(false); + assert(false); } } for (; y <= y1_; ++y) { const int iyA = flip_y_ ? out_height_ - 1 - min_iy_ - y : y - min_iy_; const float yA_lerp = ys_[iyA].lerp; const __m128 ysA_lerp = _mm_set1_ps(yA_lerp); - const T* ysA_input_lower_ptr = - input_image + ys_[iyA].lower * in_width_ * channels_; - const T* ysA_input_upper_ptr = - input_image + ys_[iyA].upper * in_width_ * channels_; - U* ysA_output_ptr = output_image + y * out_width_ * channels_; + const T *ysA_input_lower_ptr = + input_image + ys_[iyA].lower * in_width_ * channels_; + const T *ysA_input_upper_ptr = + input_image + ys_[iyA].upper * in_width_ * channels_; + U *ysA_output_ptr = output_image + y * out_width_ * channels_; if (channels_ == 1) { - this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_1ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); } else if (channels_ == 2) { - this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_2ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); } else if (channels_ == 3) { - this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_3ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); } else if (channels_ == 4) { - this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); - this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, - ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load1_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load2_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load4_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_load8_4ch_(ysA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); + this->ResizeRow_general_(yA_lerp, ysA_input_lower_ptr, + ysA_input_upper_ptr, ysA_output_ptr); } else { - assert(false); + assert(false); } } } @@ -3993,9 +3931,9 @@ void CropResizeCastImage::Resize(const T* input_image, U* output_image) { template void CropResizeCastImage::ResizeRow_general_(const float ys_lerp, - const T* ys_input_lower_ptr, - const T* ys_input_upper_ptr, - U* output_y_ptr) { + const T *ys_input_lower_ptr, + const T *ys_input_upper_ptr, + U *output_y_ptr) { for (int current = 0; current < num_general_; ++current) { int x = general_x_[current]; const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - x : x - min_ix_; @@ -4020,12 +3958,12 @@ void CropResizeCastImage::ResizeRow_general_(const float ys_lerp, // 1 channel image. template void CropResizeCastImage::ResizeRow_load1_1ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load1_; ++current) { - __m128* mmxs_lerp = - (__m128*)(load1_shuffle_masks_ + current * CHANNELS * 3); - __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; + __m128 *mmxs_lerp = + (__m128 *)(load1_shuffle_masks_ + current * CHANNELS * 3); + __m128i *shuffle_masks = (__m128i *)mmxs_lerp + CHANNELS; #ifdef __AVX2__ __m256 left0, right0; this->load1_1ch(ysA_input_lower_ptr, ysA_input_upper_ptr, @@ -4062,12 +4000,12 @@ void CropResizeCastImage::ResizeRow_load1_1ch_( // 1 channel image. template void CropResizeCastImage::ResizeRow_load2_1ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load2_; ++current) { - __m128* mmxs_lerp = - (__m128*)(load2_shuffle_masks_ + current * CHANNELS * 2); - __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; + __m128 *mmxs_lerp = + (__m128 *)(load2_shuffle_masks_ + current * CHANNELS * 2); + __m128i *shuffle_masks = (__m128i *)mmxs_lerp + CHANNELS; #ifdef __AVX2__ __m256 left0, right0; this->load2_1ch(ysA_input_lower_ptr, ysA_input_upper_ptr, @@ -4104,10 +4042,10 @@ void CropResizeCastImage::ResizeRow_load2_1ch_( // 1 channel image. template void CropResizeCastImage::ResizeRow_load4_1ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load4_; ++current) { - __m128* mmxs_lerp = (__m128*)(load4_mmxs_lerp_ + current * CHANNELS); + __m128 *mmxs_lerp = (__m128 *)(load4_mmxs_lerp_ + current * CHANNELS); #ifdef __AVX2__ __m256 left0, right0; this->load4_1ch( @@ -4147,10 +4085,10 @@ void CropResizeCastImage::ResizeRow_load4_1ch_( // 1 channel image. template void CropResizeCastImage::ResizeRow_load8_1ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load8_; ++current) { - __m128* mmxs_lerp = (__m128*)(load8_mmxs_lerp_ + current * CHANNELS); + __m128 *mmxs_lerp = (__m128 *)(load8_mmxs_lerp_ + current * CHANNELS); #ifdef __AVX2__ __m256 left0, right0; this->load8_1ch( @@ -4193,12 +4131,12 @@ void CropResizeCastImage::ResizeRow_load8_1ch_( // 2 channel image. template void CropResizeCastImage::ResizeRow_load1_2ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load1_; ++current) { - __m128* mmxs_lerp = - (__m128*)(load1_shuffle_masks_ + current * CHANNELS * 3); - __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; + __m128 *mmxs_lerp = + (__m128 *)(load1_shuffle_masks_ + current * CHANNELS * 3); + __m128i *shuffle_masks = (__m128i *)mmxs_lerp + CHANNELS; #ifdef __AVX2__ __m256 left0, left1, right0, right1; this->load1_2ch(ysA_input_lower_ptr, ysA_input_upper_ptr, @@ -4246,12 +4184,12 @@ void CropResizeCastImage::ResizeRow_load1_2ch_( // 2 channel image. template void CropResizeCastImage::ResizeRow_load2_2ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load2_; ++current) { - __m128* mmxs_lerp = - (__m128*)(load2_shuffle_masks_ + current * CHANNELS * 2); - __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; + __m128 *mmxs_lerp = + (__m128 *)(load2_shuffle_masks_ + current * CHANNELS * 2); + __m128i *shuffle_masks = (__m128i *)mmxs_lerp + CHANNELS; #ifdef __AVX2__ __m256 left0, left1, right0, right1; this->load2_2ch(ysA_input_lower_ptr, ysA_input_upper_ptr, @@ -4299,10 +4237,10 @@ void CropResizeCastImage::ResizeRow_load2_2ch_( // 2 channel image. template void CropResizeCastImage::ResizeRow_load4_2ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load4_; ++current) { - __m128* mmxs_lerp = (__m128*)(load4_mmxs_lerp_ + current * CHANNELS); + __m128 *mmxs_lerp = (__m128 *)(load4_mmxs_lerp_ + current * CHANNELS); #ifdef __AVX2__ __m256 left0, left1, right0, right1; this->load4_2ch( @@ -4353,10 +4291,10 @@ void CropResizeCastImage::ResizeRow_load4_2ch_( // 2 channel image. template void CropResizeCastImage::ResizeRow_load8_2ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load8_; ++current) { - __m128* mmxs_lerp = (__m128*)(load8_mmxs_lerp_ + current * CHANNELS); + __m128 *mmxs_lerp = (__m128 *)(load8_mmxs_lerp_ + current * CHANNELS); #ifdef __AVX2__ __m256 left0, left1, right0, right1; this->load8_2ch( @@ -4410,12 +4348,12 @@ void CropResizeCastImage::ResizeRow_load8_2ch_( // 3 channel image. template void CropResizeCastImage::ResizeRow_load1_3ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load1_; ++current) { - __m128* mmxs_lerp = - (__m128*)(load1_shuffle_masks_ + current * CHANNELS * 3); - __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; + __m128 *mmxs_lerp = + (__m128 *)(load1_shuffle_masks_ + current * CHANNELS * 3); + __m128i *shuffle_masks = (__m128i *)mmxs_lerp + CHANNELS; #ifdef __AVX2__ __m256 left0, left1, left2, right0, right1, right2; this->load1_3ch(ysA_input_lower_ptr, ysA_input_upper_ptr, @@ -4473,12 +4411,12 @@ void CropResizeCastImage::ResizeRow_load1_3ch_( // 3 channel image. template void CropResizeCastImage::ResizeRow_load2_3ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load2_; ++current) { - __m128* mmxs_lerp = - (__m128*)(load2_shuffle_masks_ + current * CHANNELS * 2); - __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; + __m128 *mmxs_lerp = + (__m128 *)(load2_shuffle_masks_ + current * CHANNELS * 2); + __m128i *shuffle_masks = (__m128i *)mmxs_lerp + CHANNELS; #ifdef __AVX2__ __m256 left0, left1, left2, right0, right1, right2; this->load2_3ch(ysA_input_lower_ptr, ysA_input_upper_ptr, @@ -4536,10 +4474,10 @@ void CropResizeCastImage::ResizeRow_load2_3ch_( // 3 channel image. template void CropResizeCastImage::ResizeRow_load4_3ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load4_; ++current) { - __m128* mmxs_lerp = (__m128*)(load4_mmxs_lerp_ + current * CHANNELS); + __m128 *mmxs_lerp = (__m128 *)(load4_mmxs_lerp_ + current * CHANNELS); #ifdef __AVX2__ __m256 left0, left1, left2, right0, right1, right2; this->load4_3ch( @@ -4601,10 +4539,10 @@ void CropResizeCastImage::ResizeRow_load4_3ch_( // 3 channel image. template void CropResizeCastImage::ResizeRow_load8_3ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load8_; ++current) { - __m128* mmxs_lerp = (__m128*)(load8_mmxs_lerp_ + current * CHANNELS); + __m128 *mmxs_lerp = (__m128 *)(load8_mmxs_lerp_ + current * CHANNELS); #ifdef __AVX2__ __m256 left0, left1, left2, right0, right1, right2; this->load8_3ch( @@ -4669,12 +4607,12 @@ void CropResizeCastImage::ResizeRow_load8_3ch_( // 4 channel image. template void CropResizeCastImage::ResizeRow_load1_4ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load1_; ++current) { - __m128* mmxs_lerp = - (__m128*)(load1_shuffle_masks_ + current * CHANNELS * 3); - __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; + __m128 *mmxs_lerp = + (__m128 *)(load1_shuffle_masks_ + current * CHANNELS * 3); + __m128i *shuffle_masks = (__m128i *)mmxs_lerp + CHANNELS; #ifdef __AVX2__ __m256 left0, left1, left2, left3, right0, right1, right2, right3; this->load1_4ch(ysA_input_lower_ptr, ysA_input_upper_ptr, @@ -4744,12 +4682,12 @@ void CropResizeCastImage::ResizeRow_load1_4ch_( // 4 channel image. template void CropResizeCastImage::ResizeRow_load2_4ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load2_; ++current) { - __m128* mmxs_lerp = - (__m128*)(load2_shuffle_masks_ + current * CHANNELS * 2); - __m128i* shuffle_masks = (__m128i*)mmxs_lerp + CHANNELS; + __m128 *mmxs_lerp = + (__m128 *)(load2_shuffle_masks_ + current * CHANNELS * 2); + __m128i *shuffle_masks = (__m128i *)mmxs_lerp + CHANNELS; #ifdef __AVX2__ __m256 left0, left1, left2, left3, right0, right1, right2, right3; this->load2_4ch(ysA_input_lower_ptr, ysA_input_upper_ptr, @@ -4819,10 +4757,10 @@ void CropResizeCastImage::ResizeRow_load2_4ch_( // 4 channel image. template void CropResizeCastImage::ResizeRow_load4_4ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load4_; ++current) { - __m128* mmxs_lerp = (__m128*)(load4_mmxs_lerp_ + current * CHANNELS); + __m128 *mmxs_lerp = (__m128 *)(load4_mmxs_lerp_ + current * CHANNELS); #ifdef __AVX2__ __m256 left0, left1, left2, left3, right0, right1, right2, right3; this->load4_4ch( @@ -4895,10 +4833,10 @@ void CropResizeCastImage::ResizeRow_load4_4ch_( // 4 channel image. template void CropResizeCastImage::ResizeRow_load8_4ch_( - const __m128 y_lerp, const T* ysA_input_lower_ptr, - const T* ysA_input_upper_ptr, U* ysA_output_ptr) { + const __m128 y_lerp, const T *ysA_input_lower_ptr, + const T *ysA_input_upper_ptr, U *ysA_output_ptr) { for (int current = 0; current < num_load8_; ++current) { - __m128* mmxs_lerp = (__m128*)(load8_mmxs_lerp_ + current * CHANNELS); + __m128 *mmxs_lerp = (__m128 *)(load8_mmxs_lerp_ + current * CHANNELS); #ifdef __AVX2__ __m256 left0, left1, left2, left3, right0, right1, right2, right3; this->load8_4ch( @@ -4969,22 +4907,23 @@ void CropResizeCastImage::ResizeRow_load8_4ch_( } #undef CHANNELS -template -void CropResizeCastImage::Configure_() { +template void CropResizeCastImage::Configure_() { // num_cases[0] = general case // num_cases[1] = load4from1 // num_cases[2] = load4from2 // num_cases[3] = load4from4 // num_cases[4] = load4from8 int num_cases[5]; - for (int i = 0; i < 5; ++i) num_cases[i] = 0; + for (int i = 0; i < 5; ++i) + num_cases[i] = 0; for (int x = x0_; x <= x1_; ++x) { int load_group = this->DetermineLoadGroup_(x); assert(load_group >= 0 && load_group <= 4); ++num_cases[load_group]; // load_group == 0 -> general case, pixel by pixel // every other value indidcates 1+3 = 4 pixels were processed this iteration - if (load_group > 0) x += 3; + if (load_group > 0) + x += 3; } num_general_ = num_cases[0]; num_load1_ = num_cases[1]; @@ -4999,7 +4938,7 @@ void CropResizeCastImage::Configure_() { if (num_load1_ > 0) { load1_offsets_ = new int[num_load1_]; load1_shuffle_masks_ = new __m128i[num_load1_ * channels_ * 3]; - load1_mmxs_lerp_ = NULL; // new __m128[num_load1_*channels_]; + load1_mmxs_lerp_ = NULL; // new __m128[num_load1_*channels_]; load1_x_ = new int[num_load1_]; } else { load1_offsets_ = NULL; @@ -5010,7 +4949,7 @@ void CropResizeCastImage::Configure_() { if (num_load2_ > 0) { load2_offsets_ = new int[num_load2_]; load2_shuffle_masks_ = new __m128i[num_load2_ * channels_ * 2]; - load2_mmxs_lerp_ = NULL; // new __m128[num_load2_*channels_]; + load2_mmxs_lerp_ = NULL; // new __m128[num_load2_*channels_]; load2_x_ = new int[num_load2_]; } else { load2_offsets_ = NULL; @@ -5036,7 +4975,8 @@ void CropResizeCastImage::Configure_() { load8_mmxs_lerp_ = NULL; load8_x_ = NULL; } - for (int i = 0; i < 5; ++i) num_cases[i] = 0; + for (int i = 0; i < 5; ++i) + num_cases[i] = 0; if (verbose_) { printf(" load4from1 = %d\n", num_load1_); printf(" load4from2 = %d\n", num_load2_); @@ -5060,17 +5000,19 @@ void CropResizeCastImage::Configure_() { int min_xidx, max_xidx; ComputeXIndexRange_(x, &min_xidx, &max_xidx); load1_offsets_[current] = min_xidx * channels_; - float* xs_lerp = (float*)(load1_shuffle_masks_ + current * channels_ * 3); - char* shufmasks1 = - (char*)(load1_shuffle_masks_ + current * channels_ * 3 + channels_); - char* shufmasks2 = shufmasks1 + 16 * channels_; - for (int j = 0; j < 32 * channels_; ++j) shufmasks1[j] = -128; + float *xs_lerp = + (float *)(load1_shuffle_masks_ + current * channels_ * 3); + char *shufmasks1 = + (char *)(load1_shuffle_masks_ + current * channels_ * 3 + channels_); + char *shufmasks2 = shufmasks1 + 16 * channels_; + for (int j = 0; j < 32 * channels_; ++j) + shufmasks1[j] = -128; for (int pix = 0; pix < 4; ++pix) { const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) : (x + pix) - min_ix_; float lerp = xs_[ix].lerp; int widx0 = xs_[ix].lower - - load1_offsets_[current]; // word index within SSE vector + load1_offsets_[current]; // word index within SSE vector for (int ch = 0; ch < channels_; ++ch) { int idx = pix * channels_ + ch; xs_lerp[idx] = lerp; @@ -5092,16 +5034,18 @@ void CropResizeCastImage::Configure_() { int min_xidx, max_xidx; ComputeXIndexRange_(x, &min_xidx, &max_xidx); load2_offsets_[current] = min_xidx * channels_; - float* xs_lerp = (float*)(load2_shuffle_masks_ + current * channels_ * 2); - char* shufmasks1 = - (char*)(load2_shuffle_masks_ + current * channels_ * 2 + channels_); - for (int j = 0; j < 16 * channels_; ++j) shufmasks1[j] = -128; + float *xs_lerp = + (float *)(load2_shuffle_masks_ + current * channels_ * 2); + char *shufmasks1 = + (char *)(load2_shuffle_masks_ + current * channels_ * 2 + channels_); + for (int j = 0; j < 16 * channels_; ++j) + shufmasks1[j] = -128; for (int pix = 0; pix < 4; ++pix) { const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) : (x + pix) - min_ix_; float lerp = xs_[ix].lerp; int widx0 = xs_[ix].lower - - load2_offsets_[current]; // word index within SSE vector + load2_offsets_[current]; // word index within SSE vector for (int ch = 0; ch < channels_; ++ch) { int idx = pix * channels_ + ch; xs_lerp[idx] = lerp; @@ -5118,8 +5062,8 @@ void CropResizeCastImage::Configure_() { // load4from4 assert(current < num_load4_); load4_x_[current] = x; - int* index = load4_offsets_ + current * 4; - float* xs_lerp = (float*)(load4_mmxs_lerp_ + current * channels_); + int *index = load4_offsets_ + current * 4; + float *xs_lerp = (float *)(load4_mmxs_lerp_ + current * channels_); for (int pix = 0; pix < 4; ++pix) { const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) : (x + pix) - min_ix_; @@ -5134,8 +5078,8 @@ void CropResizeCastImage::Configure_() { // load4from8 assert(current < num_load8_); load8_x_[current] = x; - int* index = load8_offsets_ + current * 4; - float* xs_lerp = (float*)(load8_mmxs_lerp_ + current * channels_); + int *index = load8_offsets_ + current * 4; + float *xs_lerp = (float *)(load8_mmxs_lerp_ + current * channels_); for (int pix = 0; pix < 4; ++pix) { const int ix = flip_x_ ? out_width_ - 1 - min_ix_ - (x + pix) : (x + pix) - min_ix_; @@ -5152,7 +5096,8 @@ void CropResizeCastImage::Configure_() { ++num_cases[load_group]; // load_group == 0 -> general case, pixel by pixel // every other value indidcates 1+3 = 4 pixels were processed this iteration - if (load_group > 0) x += 3; + if (load_group > 0) + x += 3; } } @@ -5198,8 +5143,8 @@ int CropResizeCastImage::DetermineLoadGroup_(const int x) { // Compute range of x indexes for xs[0] through xs[3]. // Returns true if valid (xs[i].lower + channels == xs[i].upper for all pixels). template -bool CropResizeCastImage::ComputeXIndexRange_(const int x, int* min_xidx, - int* max_xidx) { +bool CropResizeCastImage::ComputeXIndexRange_(const int x, int *min_xidx, + int *max_xidx) { bool upper_is_lower_plus_one = true; *min_xidx = 0; *max_xidx = -1; @@ -5212,8 +5157,10 @@ bool CropResizeCastImage::ComputeXIndexRange_(const int x, int* min_xidx, *min_xidx = curr_xidx; *max_xidx = curr_xidx; } else { - if (curr_xidx < *min_xidx) *min_xidx = curr_xidx; - if (curr_xidx > *max_xidx) *max_xidx = curr_xidx; + if (curr_xidx < *min_xidx) + *min_xidx = curr_xidx; + if (curr_xidx > *max_xidx) + *max_xidx = curr_xidx; } } else { upper_is_lower_plus_one = false; @@ -5313,206 +5260,158 @@ bool CropResizeCastImage::Load8_ok_(const int min_xidx, // full implementations of templated static member function clip_necessary() // -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return true; } -template <> -bool CropResizeCastImage::clip_necessary() { +template <> bool CropResizeCastImage::clip_necessary() { return false; } @@ -5524,14 +5423,14 @@ bool CropResizeCastImage::clip_necessary() { #define CROP_RESIZE_SINGLE_IMAGE_VECT(T_type, U_type) \ template <> \ void crop_resize_single_image_common( \ - const T_type* image, const int64 in_height, const int64 in_width, \ + const T_type *image, const int64 in_height, const int64 in_width, \ const int64 out_height, const int64 out_width, const int channels, \ - const int min_ix, const int max_ix, const CachedInterpolation* xs, \ - const int min_iy, const int max_iy, const CachedInterpolation* ys, \ + const int min_ix, const int max_ix, const CachedInterpolation *xs, \ + const int min_iy, const int max_iy, const CachedInterpolation *ys, \ const float extrapolated_value, const bool flip_x, const bool flip_y, \ - U_type* output) { \ + U_type *output) { \ if (channels <= 4) { \ - CropResizeCastImage* resizer = \ + CropResizeCastImage *resizer = \ new CropResizeCastImage( \ in_height, in_width, out_height, out_width, channels, min_ix, \ max_ix, xs, min_iy, max_iy, ys, extrapolated_value, flip_x, \ @@ -5560,19 +5459,19 @@ CROP_RESIZE_SINGLE_IMAGE_VECT(float, float) // image resizing for these data types default to the original code. // at the moment, this is int64 and double. -#define CROP_RESIZE_SINGLE_IMAGE_REGULAR(T_type, U_type) \ - template <> \ - void crop_resize_single_image_common( \ - const T_type* image, const int64 in_height, const int64 in_width, \ - const int64 out_height, const int64 out_width, const int channels, \ - const int min_ix, const int max_ix, const CachedInterpolation* xs, \ - const int min_iy, const int max_iy, const CachedInterpolation* ys, \ - const float extrapolated_value, const bool flip_x, const bool flip_y, \ - U_type* output) { \ - crop_resize_single_image(image, in_height, in_width, out_height, \ - out_width, channels, min_ix, max_ix, xs, min_iy, \ - max_iy, ys, extrapolated_value, flip_x, flip_y, \ - output); \ +#define CROP_RESIZE_SINGLE_IMAGE_REGULAR(T_type, U_type) \ + template <> \ + void crop_resize_single_image_common( \ + const T_type *image, const int64 in_height, const int64 in_width, \ + const int64 out_height, const int64 out_width, const int channels, \ + const int min_ix, const int max_ix, const CachedInterpolation *xs, \ + const int min_iy, const int max_iy, const CachedInterpolation *ys, \ + const float extrapolated_value, const bool flip_x, const bool flip_y, \ + U_type *output) { \ + crop_resize_single_image(image, in_height, in_width, out_height, \ + out_width, channels, min_ix, max_ix, xs, min_iy, \ + max_iy, ys, extrapolated_value, flip_x, flip_y, \ + output); \ } CROP_RESIZE_SINGLE_IMAGE_REGULAR(int64, float) @@ -5586,12 +5485,12 @@ CROP_RESIZE_SINGLE_IMAGE_REGULAR(double, float) template void crop_resize_single_image_common( - const T* image, const int64 in_height, const int64 in_width, + const T *image, const int64 in_height, const int64 in_width, const int64 out_height, const int64 out_width, const int channels, - const int min_ix, const int max_ix, const CachedInterpolation* xs, - const int min_iy, const int max_iy, const CachedInterpolation* ys, + const int min_ix, const int max_ix, const CachedInterpolation *xs, + const int min_iy, const int max_iy, const CachedInterpolation *ys, const float extrapolated_value, const bool flip_x, const bool flip_y, - U* output) { + U *output) { crop_resize_single_image(image, in_height, in_width, out_height, out_width, channels, min_ix, max_ix, xs, min_iy, max_iy, ys, extrapolated_value, flip_x, flip_y, output); @@ -5599,6 +5498,6 @@ void crop_resize_single_image_common( #endif -} // namespace -} // namespace tensorflow -#endif // define TENSORFLOW_CORE_KERNELS_CROP_RESIZE_BILINEAR_CORE_H_ +} // namespace +} // namespace tensorflow +#endif // define TENSORFLOW_CORE_KERNELS_CROP_RESIZE_BILINEAR_CORE_H_ -- GitLab From 1845bf763b4c1c54425d9bb8b1554db79759f567 Mon Sep 17 00:00:00 2001 From: Tayo Oguntebi Date: Wed, 17 Oct 2018 13:03:04 -0700 Subject: [PATCH 0217/1825] Fixes #22750 Store NonMaxSuppression op member tensors as raw pointers, instead of Tensor objects. OpKernelContext returns input tensors as objects with ref count of zero, and as such, using a raw pointer avoids calling the copy constructor on these objects. The previous behavior stores input tensors as class members with longer lifetimes and may not be thread-safe. PiperOrigin-RevId: 217572722 --- .../core/kernels/non_max_suppression_op.cc | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tensorflow/core/kernels/non_max_suppression_op.cc b/tensorflow/core/kernels/non_max_suppression_op.cc index ea08358f50..4caa04c185 100644 --- a/tensorflow/core/kernels/non_max_suppression_op.cc +++ b/tensorflow/core/kernels/non_max_suppression_op.cc @@ -285,15 +285,15 @@ class NonMaxSuppressionV3V4Base : public OpKernel { void Compute(OpKernelContext* context) override { // boxes: [num_boxes, 4] - boxes_ = context->input(0); + boxes_ = &context->input(0); // scores: [num_boxes] - scores_ = context->input(1); + scores_ = &context->input(1); // max_output_size: scalar - max_output_size_ = context->input(2); + max_output_size_ = &context->input(2); OP_REQUIRES( - context, TensorShapeUtils::IsScalar(max_output_size_.shape()), + context, TensorShapeUtils::IsScalar(max_output_size_->shape()), errors::InvalidArgument("max_output_size must be 0-D, got shape ", - max_output_size_.shape().DebugString())); + max_output_size_->shape().DebugString())); // iou_threshold: scalar const Tensor& iou_threshold = context->input(3); OP_REQUIRES(context, TensorShapeUtils::IsScalar(iou_threshold.shape()), @@ -311,8 +311,8 @@ class NonMaxSuppressionV3V4Base : public OpKernel { score_threshold_val_ = score_threshold.scalar()(); num_boxes_ = 0; - ParseAndCheckBoxSizes(context, boxes_, &num_boxes_); - CheckScoreSizes(context, num_boxes_, scores_); + ParseAndCheckBoxSizes(context, *boxes_, &num_boxes_); + CheckScoreSizes(context, num_boxes_, *scores_); if (!context->status().ok()) { return; } @@ -323,9 +323,9 @@ class NonMaxSuppressionV3V4Base : public OpKernel { protected: virtual void DoComputeAndPostProcess(OpKernelContext* context) = 0; - Tensor boxes_; - Tensor scores_; - Tensor max_output_size_; + const Tensor* boxes_; + const Tensor* scores_; + const Tensor* max_output_size_; int num_boxes_; float iou_threshold_val_; float score_threshold_val_; @@ -340,9 +340,9 @@ class NonMaxSuppressionV3Op : public NonMaxSuppressionV3V4Base { protected: void DoComputeAndPostProcess(OpKernelContext* context) override { auto suppress_check_fn = - CreateIOUSuppressCheckFn(boxes_, iou_threshold_val_); + CreateIOUSuppressCheckFn(*boxes_, iou_threshold_val_); - DoNonMaxSuppressionOp(context, scores_, num_boxes_, max_output_size_, + DoNonMaxSuppressionOp(context, *scores_, num_boxes_, *max_output_size_, score_threshold_val_, suppress_check_fn); } }; @@ -359,10 +359,10 @@ class NonMaxSuppressionV4Op : public NonMaxSuppressionV3V4Base { protected: void DoComputeAndPostProcess(OpKernelContext* context) override { auto suppress_check_fn = - CreateIOUSuppressCheckFn(boxes_, iou_threshold_val_); + CreateIOUSuppressCheckFn(*boxes_, iou_threshold_val_); int num_valid_outputs; - DoNonMaxSuppressionOp(context, scores_, num_boxes_, max_output_size_, + DoNonMaxSuppressionOp(context, *scores_, num_boxes_, *max_output_size_, score_threshold_val_, suppress_check_fn, pad_to_max_output_size_, &num_valid_outputs); -- GitLab From 8016993b7383307dfc1249f20d18ff1f4fa31d14 Mon Sep 17 00:00:00 2001 From: Tong Shen Date: Wed, 17 Oct 2018 13:09:42 -0700 Subject: [PATCH 0218/1825] Add class for outside compilation subgraph rewrite. PiperOrigin-RevId: 217573846 --- tensorflow/compiler/jit/BUILD | 8 + .../jit/extract_outside_compilation_pass.cc | 365 ++++++++++++++++++ .../jit/extract_outside_compilation_pass.h | 66 ++++ .../extract_outside_compilation_pass_test.cc | 219 +++++++++++ 4 files changed, 658 insertions(+) create mode 100644 tensorflow/compiler/jit/extract_outside_compilation_pass.cc create mode 100644 tensorflow/compiler/jit/extract_outside_compilation_pass.h create mode 100644 tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD index 9dfa0fa8c5..dddf8e69d9 100644 --- a/tensorflow/compiler/jit/BUILD +++ b/tensorflow/compiler/jit/BUILD @@ -444,6 +444,7 @@ cc_library( "deadness_analysis_internal.h", "encapsulate_subgraphs_pass.cc", "encapsulate_xla_computations_pass.cc", + "extract_outside_compilation_pass.cc", "mark_for_compilation_pass.cc", "mark_for_compilation_pass_test_helper.cc", "partially_decluster_pass.cc", @@ -453,12 +454,14 @@ cc_library( "deadness_analysis.h", "encapsulate_subgraphs_pass.h", "encapsulate_xla_computations_pass.h", + "extract_outside_compilation_pass.h", "mark_for_compilation_pass.h", "mark_for_compilation_pass_test_helper.h", "partially_decluster_pass.h", ], deps = [ ":common", + ":encapsulate_util", ":shape_inference_helpers", ":union_find", ":xla_cluster_util", @@ -471,6 +474,7 @@ cc_library( "//tensorflow/compiler/jit/ops:xla_ops", "//tensorflow/compiler/tf2xla:dump_graph", "//tensorflow/compiler/tf2xla:resource_operation_table", + "//tensorflow/compiler/tf2xla:tf2xla_util", "//tensorflow/compiler/tf2xla:xla_compiler", "//tensorflow/compiler/tf2xla/cc:xla_jit_ops", "//tensorflow/compiler/xla:status_macros", @@ -568,12 +572,14 @@ tf_cc_test( "build_xla_ops_pass_test.cc", "encapsulate_subgraphs_pass_test.cc", "encapsulate_xla_computations_pass_test.cc", + "extract_outside_compilation_pass_test.cc", "mark_for_compilation_pass_test.cc", "partially_decluster_pass_test.cc", ], deps = [ ":common", ":compilation_passes", + ":encapsulate_util", ":node_matchers", ":xla_cluster_util", ":xla_cpu_device", @@ -583,6 +589,7 @@ tf_cc_test( "//tensorflow/cc:function_ops", "//tensorflow/cc:ops", "//tensorflow/cc:resource_variable_ops", + "//tensorflow/cc:scope", "//tensorflow/cc:sendrecv_ops", "//tensorflow/compiler/jit/kernels:xla_ops", "//tensorflow/compiler/tf2xla:test_util", @@ -594,6 +601,7 @@ tf_cc_test( "//tensorflow/core:framework", "//tensorflow/core:framework_internal", "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", "//tensorflow/core:test", "//tensorflow/core:test_main", "//tensorflow/core:testlib", diff --git a/tensorflow/compiler/jit/extract_outside_compilation_pass.cc b/tensorflow/compiler/jit/extract_outside_compilation_pass.cc new file mode 100644 index 0000000000..3963fb012e --- /dev/null +++ b/tensorflow/compiler/jit/extract_outside_compilation_pass.cc @@ -0,0 +1,365 @@ +/* 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/jit/extract_outside_compilation_pass.h" + +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "tensorflow/compiler/jit/encapsulate_util.h" +#include "tensorflow/compiler/tf2xla/tf2xla_util.h" +#include "tensorflow/core/framework/node_def_builder.h" +#include "tensorflow/core/framework/tensor_shape.pb.h" +#include "tensorflow/core/graph/algorithm.h" + +namespace tensorflow { + +namespace { + +// Add a key placeholder node to the graph. The key placeholder node will be +// used as input for XlaRecvAtHost/XlaSendFromHost nodes. +xla::StatusOr AddHostComputeKeyPlaceholder( + const string& xla_cluster_name, Graph* g) { + NodeDef key_def; + NodeDefBuilder builder(absl::StrCat(xla_cluster_name, "_key_placeholder"), + "Placeholder"); + builder.Attr("dtype", DT_STRING); + builder.Attr("shape", PartialTensorShape({2})); + builder.Attr("_host_compute_call_node", xla_cluster_name); + Status s = builder.Finalize(&key_def); + if (!s.ok()) return s; + + Node* n = g->AddNode(key_def, &s); + if (!s.ok()) return s; + return n; +} + +// Returns nodes with given type. +std::vector GatherNodesWithType(const Graph& g, const string& type) { + std::vector result; + for (Node* n : g.nodes()) { + if (n->type_string() == type) { + result.push_back(n); + } + } + return result; +} + +// Gets data types from `arg_nodes` and fills them into `recv_at_host_dtypes`. +Status GetArgDataTypes(const std::vector& arg_nodes, + std::vector* recv_at_host_dtypes) { + recv_at_host_dtypes->resize(arg_nodes.size(), DT_INVALID); + for (auto* n : arg_nodes) { + int index; + TF_RETURN_IF_ERROR(GetNodeAttr(n->attrs(), "index", &index)); + DataType dtype; + TF_RETURN_IF_ERROR(GetNodeAttr(n->attrs(), "T", &dtype)); + (*recv_at_host_dtypes)[index] = dtype; + } + for (int i = 0; i < recv_at_host_dtypes->size(); i++) { + if ((*recv_at_host_dtypes)[i] == DT_INVALID) { + return errors::Internal("Cannot get datatype for input ", i); + } + } + return Status::OK(); +} + +// Builds XlaRecvAtHost node. +xla::StatusOr BuildRecvAtHostNode( + Graph* g, const string& oc_cluster_name, + const std::vector& recv_at_host_dtypes, Node* key_placeholder) { + NodeDefBuilder recv_at_host_builder( + absl::StrCat("outside_compilation_", oc_cluster_name, "_recv"), + "_XlaRecvAtHost"); + NodeDef recv_at_host_def; + recv_at_host_builder.Attr("Toutputs", recv_at_host_dtypes); + // The correct device_ordinal will be inserted during replication in a + // subsequent rewrite. + recv_at_host_builder.Attr("device_ordinal", 0); + recv_at_host_builder.Attr( + "key", absl::StrCat("host_compute_channel_", oc_cluster_name)); + recv_at_host_builder.Input(key_placeholder->name(), 0, DT_STRING); + TF_RETURN_IF_ERROR(recv_at_host_builder.Finalize(&recv_at_host_def)); + Status s; + Node* recv_at_host_node = g->AddNode(recv_at_host_def, &s); + TF_RETURN_IF_ERROR(s); + return recv_at_host_node; +} + +// Builds XlaRecvAtHost node, and replaces all _Arg nodes with it. +xla::StatusOr ReplaceArgNodesWithRecvAtHostNode( + Graph* g, const string& oc_cluster_name, + std::vector* recv_at_host_dtypes, Node* key_placeholder) { + std::vector arg_nodes = GatherNodesWithType(*g, "_Arg"); + TF_RETURN_IF_ERROR(GetArgDataTypes(arg_nodes, recv_at_host_dtypes)); + TF_ASSIGN_OR_RETURN( + Node * recv_at_host_node, + BuildRecvAtHostNode(g, oc_cluster_name, *recv_at_host_dtypes, + key_placeholder)); + for (auto* n : arg_nodes) { + int index; + TF_RETURN_IF_ERROR(GetNodeAttr(n->attrs(), "index", &index)); + // Record out edges and remove `n` before adding those edges to RecvAtHost. + // This is to avoid multiple producers. + std::vector out_edge_info; + for (auto edge : n->out_edges()) { + out_edge_info.push_back( + {edge->dst(), edge->src_output(), edge->dst_input()}); + } + g->RemoveNode(n); + for (const OutEdgeInfo& edge : out_edge_info) { + if (edge.dst_input == Graph::kControlSlot) { + g->AddControlEdge(recv_at_host_node, edge.dst); + } else { + g->AddEdge(recv_at_host_node, index, edge.dst, edge.dst_input); + } + } + + // Rewrite dst nodes because their input changed. + for (int i = 0; i < out_edge_info.size(); i++) { + const OutEdgeInfo edge = out_edge_info[i]; + if (edge.dst_input == Graph::kControlSlot) { + continue; + } + + Node* dst = edge.dst; + NodeDef new_def = dst->def(); + *new_def.mutable_input(edge.dst_input) = + absl::StrCat(recv_at_host_node->name(), ":", index); + TF_ASSIGN_OR_RETURN(Node * dst_replace, ReplaceNode(g, dst, new_def)); + + // Other edges might have `dst` as dst node as well. Update those edges + // with `dst_replace`. + for (int j = i + 1; j < out_edge_info.size(); j++) { + if (out_edge_info[j].dst == dst) { + out_edge_info[j].dst = dst_replace; + } + } + } + } + g->AddEdge(key_placeholder, 0, recv_at_host_node, 0); + return recv_at_host_node; +} + +// Gets data types from `ret_nodes` and fills them into `send_from_host_dtypes`. +Status GetRetDataTypes(const std::vector& ret_nodes, + std::vector* send_from_host_dtypes) { + send_from_host_dtypes->resize(ret_nodes.size(), DT_INVALID); + for (auto* n : ret_nodes) { + int index; + TF_RETURN_IF_ERROR(GetNodeAttr(n->attrs(), "index", &index)); + DataType dtype; + TF_RETURN_IF_ERROR(GetNodeAttr(n->attrs(), "T", &dtype)); + (*send_from_host_dtypes)[index] = dtype; + } + for (int i = 0; i < send_from_host_dtypes->size(); i++) { + if ((*send_from_host_dtypes)[i] == DT_INVALID) { + return errors::Internal("Cannot get datatype for output ", i); + } + } + return Status::OK(); +} + +// Builds XlaSendFromHost node. +xla::StatusOr BuildSendFromHostNode( + Graph* g, const string& oc_cluster_name, + const std::vector& ret_nodes, + const std::vector& send_from_host_dtypes, Node* key_placeholder) { + NodeDefBuilder send_from_host_builder( + absl::StrCat("outside_compilation_", oc_cluster_name, "_send"), + "_XlaSendFromHost"); + NodeDef send_from_host_def; + send_from_host_builder.Attr("Tinputs", send_from_host_dtypes); + // The correct device_ordinal will be inserted during replication in a + // subsequent rewrite. + send_from_host_builder.Attr("device_ordinal", 0); + send_from_host_builder.Attr( + "key", absl::StrCat("host_compute_channel_", oc_cluster_name)); + std::vector inputs(send_from_host_dtypes.size()); + for (auto* n : ret_nodes) { + int index; + TF_RETURN_IF_ERROR(GetNodeAttr(n->attrs(), "index", &index)); + if (index < 0 || index >= send_from_host_dtypes.size()) { + return errors::Internal("Invalid _Retval index: ", index); + } + for (auto edge : n->in_edges()) { + inputs[index] = + NodeDefBuilder::NodeOut{edge->src()->name(), edge->src_output(), + edge->src()->output_type(edge->src_output())}; + } + } + send_from_host_builder.Input(inputs); + send_from_host_builder.Input(key_placeholder->name(), 0, DT_STRING); + TF_RETURN_IF_ERROR(send_from_host_builder.Finalize(&send_from_host_def)); + Status s; + Node* send_from_host_node = g->AddNode(send_from_host_def, &s); + TF_RETURN_IF_ERROR(s); + return send_from_host_node; +} + +// Builds XlaSendFromHost node, and replaces all _Retval nodes with it. +xla::StatusOr ReplaceRetNodesWithSendFromHostNode( + Graph* g, const string& oc_cluster_name, + std::vector* send_from_host_dtypes, Node* key_placeholder) { + std::vector ret_nodes = GatherNodesWithType(*g, "_Retval"); + TF_RETURN_IF_ERROR(GetRetDataTypes(ret_nodes, send_from_host_dtypes)); + TF_ASSIGN_OR_RETURN( + Node * send_from_host_node, + BuildSendFromHostNode(g, oc_cluster_name, ret_nodes, + *send_from_host_dtypes, key_placeholder)); + for (auto* n : ret_nodes) { + int index; + TF_RETURN_IF_ERROR(GetNodeAttr(n->attrs(), "index", &index)); + for (auto edge : n->in_edges()) { + if (edge->src_output() == Graph::kControlSlot) { + g->AddControlEdge(edge->src(), send_from_host_node); + } else { + g->AddEdge(edge->src(), edge->src_output(), send_from_host_node, index); + } + } + g->RemoveNode(n); + } + g->AddEdge(key_placeholder, 0, send_from_host_node, + send_from_host_dtypes->size()); + return send_from_host_node; +} + +// Returns input shapes (excluding key placeholder) for `send_from_host_node` +// if they are all fully defined; absl::nullopt otherwise. +absl::optional> GetInferredInputShapes( + int num_inputs, Node* send_from_host_node) { + std::vector results(num_inputs); + for (int i = 0; i < num_inputs; i++) { + const Edge* e; + if (!send_from_host_node->input_edge(i, &e).ok()) { + return absl::nullopt; + } + + std::vector shapes; + if (!GetNodeAttr(e->src()->attrs(), kXlaInferredShapesAttrName, &shapes) + .ok()) { + return absl::nullopt; + } + + const PartialTensorShape shape = shapes[e->dst_input()]; + if (!shape.IsFullyDefined()) { + return absl::nullopt; + } + + results[e->dst_input()] = shape; + } + return results; +} + +} // namespace + +Status RewriteOutsideCompilationSubgraphFn::operator()( + const std::vector& arg_source_tensors, + std::unique_ptr* graph, std::vector* input_permutation, + std::vector* output_permutation, NodeDef* node_def) { + string old_name = node_def->op(); + string new_name = absl::StrCat(xla_cluster_name_, "_", old_name); + node_def->set_op(new_name); + node_def->set_name(new_name); + + // Later we will run PruneForReverseReachability(), so make sure all original + // nodes are reachable from sink node and won't be removed. + FixupSourceAndSinkEdges(graph->get()); + + // Step 1: create a key placeholder node. + TF_ASSIGN_OR_RETURN( + Node * key_placeholder, + AddHostComputeKeyPlaceholder(xla_cluster_name_, graph->get())); + + // Step 2: build RecvAtHost node, and replace all _Arg nodes with it. + std::vector recv_at_host_dtypes; + TF_ASSIGN_OR_RETURN( + Node * recv_at_host_node, + ReplaceArgNodesWithRecvAtHostNode(graph->get(), new_name, + &recv_at_host_dtypes, key_placeholder)); + + // Step 3: build SendFromHost node, and replace all _Retval nodes with it. + std::vector send_from_host_dtypes; + TF_ASSIGN_OR_RETURN( + Node * send_from_host_node, + ReplaceRetNodesWithSendFromHostNode( + graph->get(), new_name, &send_from_host_dtypes, key_placeholder)); + + // Step 4: add XLA cluster and outside compilation attr. + for (Node* n : (*graph)->nodes()) { + if (n->type_string() == "Placeholder" && + absl::EndsWith(n->name(), "_key_placeholder")) { + continue; + } + + n->AddAttr(xla_cluster_attr_name_, xla_cluster_name_); + n->AddAttr(outside_compilation_attr_name_, old_name); + } + + // Check whether we have all input shapes for XlaSendFromHost. If we do, we + // will set `shapes` attr for the call node; otherwise we will save the + // shape inference graph and set `shape_inference_graph` for the call node. + absl::optional> shapes = + GetInferredInputShapes(send_from_host_dtypes.size(), send_from_host_node); + + // Step 5: add control edges for originally XLA <-> outside compilation + // control edges. + for (Node* n : (*graph)->nodes()) { + if (HasNodeAttr(n->def(), kXlaConnectedToXlaComputationAttrName)) { + (*graph)->AddControlEdge(n, send_from_host_node); + n->ClearAttr(kXlaConnectedToXlaComputationAttrName); + } + if (HasNodeAttr(n->def(), kXlaConnectedFromXlaComputationAttrName)) { + (*graph)->AddControlEdge(recv_at_host_node, n); + n->ClearAttr(kXlaConnectedFromXlaComputationAttrName); + } + } + + // Step 6: RecvAtHost/SendFromHost/key_placeholder might be dead nodes. Prune + // them if necessary. + // - RecvAtHost should be pruned iff it has no output data/control edges. If + // it has any output edge, it will be reverse reachable from sink node. We + // don't need to do anything special. + // - SendFromHost should be pruned iff it has no input data/control edges. If + // it has input edges other than key_placeholder, we connect it to sink + // node so it won't be pruned. + // - key_placeholder should be pruned iff RecvAtHost/SendFromHost are pruned. + // We don't need to do anything special. + if (send_from_host_node->in_edges().size() > 1) { + (*graph)->AddControlEdge(send_from_host_node, (*graph)->sink_node()); + } + PruneForReverseReachability( + graph->get(), std::unordered_set{(*graph)->sink_node()}); + + // Step 7: add necessary attributes to function call node, so we can replace + // it with HostCompute node later. + AddNodeAttr("_outside_compilation_subgraph", old_name, node_def); + if (shapes) { + AddNodeAttr("shape_inference_graph", "", node_def); + AddNodeAttr("shapes", *shapes, node_def); + } else { + string shape_inference_func_name = + absl::StrCat("_outside_compilation_shape_inference_", new_name); + AddNodeAttr("shape_inference_graph", shape_inference_func_name, node_def); + AddNodeAttr("shapes", std::vector{}, node_def); + } + AddNodeAttr("ancestors", std::vector{}, node_def); + AddNodeAttr("Tinputs", recv_at_host_dtypes, node_def); + AddNodeAttr("Toutputs", send_from_host_dtypes, node_def); + AddNodeAttr("key", absl::StrCat("host_compute_channel_", new_name), node_def); + + return Status::OK(); +} + +} // namespace tensorflow diff --git a/tensorflow/compiler/jit/extract_outside_compilation_pass.h b/tensorflow/compiler/jit/extract_outside_compilation_pass.h new file mode 100644 index 0000000000..4aa34d76c6 --- /dev/null +++ b/tensorflow/compiler/jit/extract_outside_compilation_pass.h @@ -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. +==============================================================================*/ + +#ifndef TENSORFLOW_COMPILER_JIT_EXTRACT_OUTSIDE_COMPILATION_PASS_H_ +#define TENSORFLOW_COMPILER_JIT_EXTRACT_OUTSIDE_COMPILATION_PASS_H_ + +#include "absl/types/optional.h" +#include "tensorflow/compiler/xla/status_macros.h" +#include "tensorflow/core/graph/graph.h" + +namespace tensorflow { + +// Rewrite function for outside compilation subgraphs. It will perform the +// following steps: +// +// 1. Add a XLA computation key placeholder node (it will be used as input for +// XlaRecvAtHost and XlaSendFromHost); +// 2. Replace all _Arg nodes with one single XlaRecvAtHost node; +// 3. Replace all _Retval nodes with one single XlaSendFromHost node; +// 4. Mark all nodes except key placeholder with attr `xla_cluster_attr_name` +// and `outside_compilation_attr_name`; +// 5. For nodes marked with attr kXlaConnectedToXlaComputationAttrName, add a +// control edge from the node to XlaSendFromHost; for nodes marked with attr +// kXlaConnectedFromXlaComputationAttrName, add a control edge from +// XlaRecvAtHost node to the node; +// 6. Try pruning XlaRecvAtHost/XlaSendFromHost/key placeholder node. +// 7. Add necessary attributes to `node_def`, so we can replace it with a +// XlaHostCompute node later. If all input shapes for XlaSendFromHost are +// known, "shapes" attr will be set to the list of input shapes; otherwise +// "shape_inference_graph" attr will be set to shape inference function name. +class RewriteOutsideCompilationSubgraphFn { + public: + RewriteOutsideCompilationSubgraphFn( + const string& xla_cluster_attr_name, + const string& outside_compilation_attr_name, + const string& xla_cluster_name) + : xla_cluster_attr_name_(xla_cluster_attr_name), + outside_compilation_attr_name_(outside_compilation_attr_name), + xla_cluster_name_(xla_cluster_name) {} + + Status operator()(const std::vector&, + std::unique_ptr* graph, + std::vector* input_permutation, + std::vector* output_permutation, NodeDef* node_def); + + private: + string xla_cluster_attr_name_; + string outside_compilation_attr_name_; + string xla_cluster_name_; +}; + +} // namespace tensorflow + +#endif // TENSORFLOW_COMPILER_JIT_EXTRACT_OUTSIDE_COMPILATION_PASS_H_ diff --git a/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc b/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc new file mode 100644 index 0000000000..64913f5aab --- /dev/null +++ b/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc @@ -0,0 +1,219 @@ +/* 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/jit/extract_outside_compilation_pass.h" + +#include "tensorflow/cc/framework/scope.h" +#include "tensorflow/cc/ops/array_ops.h" +#include "tensorflow/cc/ops/function_ops.h" +#include "tensorflow/cc/ops/standard_ops.h" +#include "tensorflow/compiler/jit/encapsulate_util.h" +#include "tensorflow/core/framework/common_shape_fns.h" +#include "tensorflow/core/framework/node_def_util.h" +#include "tensorflow/core/framework/tensor_shape.h" +#include "tensorflow/core/framework/tensor_shape.pb.h" +#include "tensorflow/core/platform/test.h" + +namespace tensorflow { + +TEST(RewriteOutsideCompilationSubgraphFnTest, Basic) { + // Build the graph: + // "add" = "arg0" + "arg1" + // "ret0" = "add" + // "ret1" = "arg1" + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output arg0 = ops::_Arg(s.WithOpName("arg0"), DT_INT32, 0); + Output arg1 = ops::_Arg(s.WithOpName("arg1"), DT_FLOAT, 1); + Output arg2 = ops::_Arg(s.WithOpName("arg2"), DT_INT32, 2); + Output add = ops::Add(s.WithOpName("add"), arg0, arg0); + auto ret0 = ops::_Retval(s.WithOpName("ret0"), add, 0); + auto ret1 = ops::_Retval(s.WithOpName("ret1"), arg1, 1); + std::unique_ptr g(new Graph(OpRegistry::Global())); + TF_CHECK_OK(s.ToGraph(g.get())); + auto node_name_image = g->BuildNodeNameIndex(); + Node *add_node = node_name_image["add"]; + EXPECT_NE(add_node, nullptr); + add_node->AddAttr(kXlaConnectedToXlaComputationAttrName, "cluster"); + add_node->AddAttr(kXlaConnectedFromXlaComputationAttrName, "cluster"); + + RewriteOutsideCompilationSubgraphFn rewrite_fn("_xla", "_oc", "cluster"); + std::vector arg_source_tensors; + NodeDef call_node_def; + call_node_def.set_op("0"); + TF_CHECK_OK( + rewrite_fn(arg_source_tensors, &g, nullptr, nullptr, &call_node_def)); + node_name_image = g->BuildNodeNameIndex(); + + // Verify step 1: add key placeholder node. + Node *key_placeholder = node_name_image["cluster_key_placeholder"]; + EXPECT_NE(key_placeholder, nullptr); + // Verify step 2: replace _Arg nodes with XlaRecvAtHost. + for (Node *n : g->nodes()) { + EXPECT_NE(n->type_string(), "_Arg"); + } + Node *recv_at_host = node_name_image["outside_compilation_cluster_0_recv"]; + EXPECT_NE(recv_at_host, nullptr); + std::vector recv_at_host_dtypes; + TF_CHECK_OK( + GetNodeAttr(recv_at_host->attrs(), "Toutputs", &recv_at_host_dtypes)); + EXPECT_EQ(recv_at_host_dtypes.size(), 3); + EXPECT_EQ(recv_at_host_dtypes[0], DT_INT32); + EXPECT_EQ(recv_at_host_dtypes[1], DT_FLOAT); + EXPECT_EQ(recv_at_host_dtypes[2], DT_INT32); + // Verify step 3: replace _Retval nodes with XlaSendFromHost. + for (Node *n : g->nodes()) { + EXPECT_NE(n->type_string(), "_Retval"); + } + Node *send_from_host = node_name_image["outside_compilation_cluster_0_send"]; + EXPECT_NE(send_from_host, nullptr); + std::vector send_from_host_dtypes; + TF_CHECK_OK( + GetNodeAttr(send_from_host->attrs(), "Tinputs", &send_from_host_dtypes)); + EXPECT_EQ(send_from_host_dtypes.size(), 2); + EXPECT_EQ(send_from_host_dtypes[0], DT_INT32); + EXPECT_EQ(send_from_host_dtypes[1], DT_FLOAT); + // Verify step 4: nodes marked with XLA cluster and outside compilation attr. + add_node = node_name_image["add"]; + EXPECT_NE(add_node, nullptr); + EXPECT_TRUE(HasNodeAttr(add_node->def(), "_xla")); + EXPECT_TRUE(HasNodeAttr(add_node->def(), "_oc")); + // Verify step 5: control edges added. + bool has_control_edge_from_recv_at_host = false; + for (auto e : add_node->in_edges()) { + if (e->IsControlEdge() && e->src() == recv_at_host) { + has_control_edge_from_recv_at_host = true; + } + } + EXPECT_TRUE(has_control_edge_from_recv_at_host); + bool has_control_edge_to_send_from_host = false; + for (auto e : add_node->out_edges()) { + if (e->IsControlEdge() && e->dst() == send_from_host) { + has_control_edge_to_send_from_host = true; + } + } + EXPECT_TRUE(has_control_edge_to_send_from_host); + // Verify step 7: necessary attrs added to call_node_def. + string shape_inference_graph; + TF_CHECK_OK(GetNodeAttr(AttrSlice(&call_node_def.attr()), + "shape_inference_graph", &shape_inference_graph)); + EXPECT_EQ(shape_inference_graph, + "_outside_compilation_shape_inference_cluster_0"); +} + +TEST(RewriteOutsideCompilationSubgraphFnTest, NoSendFromHost) { + // Build the graph: only 1 node: "arg0" + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output arg0 = ops::_Arg(s.WithOpName("arg0"), DT_INT32, 0); + std::unique_ptr g(new Graph(OpRegistry::Global())); + TF_CHECK_OK(s.ToGraph(g.get())); + + RewriteOutsideCompilationSubgraphFn rewrite_fn("_xla", "_oc", "cluster"); + std::vector arg_source_tensors; + NodeDef call_node_def; + call_node_def.set_op("0"); + TF_CHECK_OK( + rewrite_fn(arg_source_tensors, &g, nullptr, nullptr, &call_node_def)); + auto node_name_image = g->BuildNodeNameIndex(); + + // Check key placeholder and RecvAtHost is present, but SendFromHost is not. + Node *key_placeholder = node_name_image["cluster_key_placeholder"]; + EXPECT_NE(key_placeholder, nullptr); + Node *recv_at_host = node_name_image["outside_compilation_cluster_0_recv"]; + EXPECT_NE(recv_at_host, nullptr); + Node *send_from_host = node_name_image["outside_compilation_cluster_0_send"]; + EXPECT_EQ(send_from_host, nullptr); +} + +TEST(RewriteOutsideCompilationSubgraphFnTest, NoRecvAtHost) { + // Build the graph: + // "ret" = "const0" + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output const0 = ops::Const(s.WithOpName("const0"), 1, {2}); + auto ret = ops::_Retval(s.WithOpName("ret"), const0, 0); + std::unique_ptr g(new Graph(OpRegistry::Global())); + TF_CHECK_OK(s.ToGraph(g.get())); + + RewriteOutsideCompilationSubgraphFn rewrite_fn("_xla", "_oc", "cluster"); + std::vector arg_source_tensors; + NodeDef call_node_def; + call_node_def.set_op("0"); + TF_CHECK_OK( + rewrite_fn(arg_source_tensors, &g, nullptr, nullptr, &call_node_def)); + auto node_name_image = g->BuildNodeNameIndex(); + + // Check key placeholder and SendFromHost is present, but RecvAtHost is not. + Node *key_placeholder = node_name_image["cluster_key_placeholder"]; + EXPECT_NE(key_placeholder, nullptr); + Node *recv_at_host = node_name_image["outside_compilation_cluster_0_recv"]; + EXPECT_EQ(recv_at_host, nullptr); + Node *send_from_host = node_name_image["outside_compilation_cluster_0_send"]; + EXPECT_NE(send_from_host, nullptr); +} + +TEST(RewriteOutsideCompilationSubgraphFnTest, NoKeyPlaceholder) { + // Build the graph: only 1 node: "const0" + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output const0 = ops::Const(s.WithOpName("const0"), 1, {2}); + std::unique_ptr g(new Graph(OpRegistry::Global())); + TF_CHECK_OK(s.ToGraph(g.get())); + + RewriteOutsideCompilationSubgraphFn rewrite_fn("_xla", "_oc", "cluster"); + std::vector arg_source_tensors; + NodeDef call_node_def; + call_node_def.set_op("0"); + TF_CHECK_OK( + rewrite_fn(arg_source_tensors, &g, nullptr, nullptr, &call_node_def)); + auto node_name_image = g->BuildNodeNameIndex(); + + // Check key placeholder/RecvAtHost/SendFromHost are not present. + Node *key_placeholder = node_name_image["cluster_key_placeholder"]; + EXPECT_EQ(key_placeholder, nullptr); + Node *recv_at_host = node_name_image["outside_compilation_cluster_0_recv"]; + EXPECT_EQ(recv_at_host, nullptr); + Node *send_from_host = node_name_image["outside_compilation_cluster_0_send"]; + EXPECT_EQ(send_from_host, nullptr); +} + +TEST(RewriteOutsideCompilationSubgraphFnTest, ShapesInferred) { + // Build the graph: + // "ret" = "const0" + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output const0 = ops::Const(s.WithOpName("const0"), 1, {2}); + auto ret = ops::_Retval(s.WithOpName("ret"), const0, 0); + std::unique_ptr g(new Graph(OpRegistry::Global())); + TF_CHECK_OK(s.ToGraph(g.get())); + auto node_name_image = g->BuildNodeNameIndex(); + Node *const0_node = node_name_image["const0"]; + EXPECT_NE(const0_node, nullptr); + PartialTensorShape shape({2}); + const0_node->AddAttr(kXlaInferredShapesAttrName, + std::vector{shape}); + + RewriteOutsideCompilationSubgraphFn rewrite_fn("_xla", "_oc", "cluster"); + std::vector arg_source_tensors; + NodeDef call_node_def; + call_node_def.set_op("0"); + TF_CHECK_OK( + rewrite_fn(arg_source_tensors, &g, nullptr, nullptr, &call_node_def)); + node_name_image = g->BuildNodeNameIndex(); + + // Check "shape" attr is available in call_node_def. + std::vector shapes; + TF_CHECK_OK(GetNodeAttr(AttrSlice(&call_node_def.attr()), "shapes", &shapes)); + EXPECT_EQ(shapes.size(), 1); + EXPECT_EQ(shapes[0].dim_size(), 1); +} + +} // namespace tensorflow -- GitLab From 6649eda889c8195fb22e1f880618ea3ce650ac3f Mon Sep 17 00:00:00 2001 From: Russell Power Date: Wed, 17 Oct 2018 13:16:55 -0700 Subject: [PATCH 0219/1825] Make worker heartbeats robust to shutdowns. The coordinator sends worker heartbeats periodically in a loop. Previously if one of these heartbeats timed out (due to a lame worker), the entire loop would exit and force the remaining workers to exit after the timeout. This removes the coordinator from the heartbeat set and catches any TF errors that occur during the heartbeat. PiperOrigin-RevId: 217574934 --- .../contrib/tpu/python/tpu/session_support.py | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/tensorflow/contrib/tpu/python/tpu/session_support.py b/tensorflow/contrib/tpu/python/tpu/session_support.py index 8248256373..a952754878 100644 --- a/tensorflow/contrib/tpu/python/tpu/session_support.py +++ b/tensorflow/contrib/tpu/python/tpu/session_support.py @@ -177,7 +177,10 @@ class WorkerHeartbeatManager(object): def all_worker_devices(session): """Return a list of devices for each worker in the system.""" devices = session.list_devices() - return [device.name for device in devices if ':CPU:' in device.name] + return [ + device.name for device in devices + if ':CPU:' in device.name and 'coordinator' not in device.name + ] class WatchdogManager(threading.Thread): @@ -217,45 +220,68 @@ class WatchdogManager(threading.Thread): self.ping_interval = ping_interval self.shutdown_timeout = shutdown_timeout self.daemon = True + self._target = session.sess_str self._running = False + self._devices = devices + + self._graph = None + self._session = None + self._worker_manager = None + + def _reset_manager(self): + """Reset the graph, session and worker manager.""" self._graph = ops.Graph() self._session = session_lib.Session( - target=session.sess_str, + target=self._target, graph=self._graph, ) + if self._devices is None: + self._devices = all_worker_devices(self._session) + with self._graph.as_default(): - if devices is None: - devices = all_worker_devices(self._session) self._worker_manager = WorkerHeartbeatManager.from_devices( - self._session, devices) + self._session, self._devices) - def configure_and_run(self): - logging.info('Enabling worker watchdog.') - self._running = True self._worker_manager.configure( event_pb2.WorkerHeartbeatRequest( watchdog_config=event_pb2.WatchdogConfig( timeout_ms=self.shutdown_timeout * 1000,))) + def configure_and_run(self): + logging.info('Enabling watchdog timer with %d second timeout ' + 'and %d second ping interval.', + self.shutdown_timeout, self.ping_interval) + self._reset_manager() + self._running = True self.start() - def __enter__(self): - self.configure_and_run() - - def __exit__(self, exc_type, exc_val, exc_tb): - logging.info('Disabling worker watchdog.') + def stop(self): + logging.info('Stopping worker watchdog.') self._worker_manager.configure( event_pb2.WorkerHeartbeatRequest( watchdog_config=event_pb2.WatchdogConfig(timeout_ms=-1,))) self._running = False self.join() + def __enter__(self): + self.configure_and_run() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() + def run(self): # Don't fetch logs or adjust timing: just ping the watchdog. + # + # If we hit an exception, reset our session as it is likely broken. while self._running: - self._worker_manager.ping(request=None) - time.sleep(self.ping_interval) + try: + self._worker_manager.ping(request=None) + time.sleep(self.ping_interval) + except errors.OpError as e: + # Catch any TF errors that occur so we don't stop sending heartbeats + logging.debug('Caught error while sending heartbeat: %s', e) + self._reset_manager() def start_worker_watchdog(session, @@ -267,8 +293,6 @@ def start_worker_watchdog(session, if _WATCHDOG is None: # Ensure we can send a few pings before we timeout! ping_interval = min(shutdown_timeout / 10., ping_interval) - logging.info('Enabling watchdog timer with %d second timeout', - shutdown_timeout) _WATCHDOG = WatchdogManager(session, devices, ping_interval, shutdown_timeout) _WATCHDOG.configure_and_run() -- GitLab From 4a51abb1f935818b6a130f71b340b29379e52a07 Mon Sep 17 00:00:00 2001 From: David Rees Date: Wed, 17 Oct 2018 13:30:06 -0700 Subject: [PATCH 0220/1825] Avoid depending on init_tensorflow in benchmark_main in BUILD file as well. PiperOrigin-RevId: 217577100 --- tensorflow/contrib/lite/tools/benchmark/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/tensorflow/contrib/lite/tools/benchmark/BUILD b/tensorflow/contrib/lite/tools/benchmark/BUILD index 166eaeea75..a3822f4215 100644 --- a/tensorflow/contrib/lite/tools/benchmark/BUILD +++ b/tensorflow/contrib/lite/tools/benchmark/BUILD @@ -32,7 +32,6 @@ cc_binary( deps = [ ":benchmark_tflite_model_lib", ":logging", - "//tensorflow/contrib/lite/testing:init_tensorflow", ], ) -- GitLab From 9fafb44f8723d8f4c9863585c912ed0fbdd0a323 Mon Sep 17 00:00:00 2001 From: Yanan Cao Date: Wed, 17 Oct 2018 13:38:53 -0700 Subject: [PATCH 0221/1825] Fix tpu_feed.py dependency PiperOrigin-RevId: 217578782 --- tensorflow/contrib/tpu/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tensorflow/contrib/tpu/BUILD b/tensorflow/contrib/tpu/BUILD index 401afcb0f4..cb5f644ee8 100644 --- a/tensorflow/contrib/tpu/BUILD +++ b/tensorflow/contrib/tpu/BUILD @@ -69,8 +69,6 @@ py_library( deps = [ ":async_checkpoint", ":tpu_lib", - "//tensorflow/compiler/xla/experimental/xla_sharding", - "//tensorflow/compiler/xla/python_api:xla_shape", "//tensorflow/contrib/training:training_py", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", @@ -258,6 +256,8 @@ py_library( ":datasets", ":profiler", ":tpu_py", + "//tensorflow/compiler/xla/experimental/xla_sharding", + "//tensorflow/compiler/xla/python_api:xla_shape", "//tensorflow/contrib/cluster_resolver:tpu_cluster_resolver_py", "//tensorflow/contrib/tpu/proto:compilation_result_proto_py", "//tensorflow/contrib/tpu/proto:optimization_parameters_proto_py", -- GitLab From 610ebb2101d9ea56e8f1081b0a77877739d98670 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Wed, 17 Oct 2018 13:56:37 -0700 Subject: [PATCH 0222/1825] [TF:XLA] Fix typo in function name. PiperOrigin-RevId: 217582093 --- tensorflow/compiler/tf2xla/tf2xla_util.cc | 2 +- tensorflow/compiler/tf2xla/tf2xla_util.h | 2 +- tensorflow/compiler/tf2xla/xla_cpu_backend.cc | 4 ++-- tensorflow/compiler/tf2xla/xla_gpu_backend.cc | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tensorflow/compiler/tf2xla/tf2xla_util.cc b/tensorflow/compiler/tf2xla/tf2xla_util.cc index 34a0f81c0a..0394b6b533 100644 --- a/tensorflow/compiler/tf2xla/tf2xla_util.cc +++ b/tensorflow/compiler/tf2xla/tf2xla_util.cc @@ -293,7 +293,7 @@ Status SetNodeShardingFromNeighbors(Node* n, bool out_edges) { return Status::OK(); } -void AddDtypeToKernalDefConstraint(absl::string_view name, DataType dtype, +void AddDtypeToKernelDefConstraint(absl::string_view name, DataType dtype, KernelDef* kdef) { for (KernelDef::AttrConstraint& constraint : *kdef->mutable_constraint()) { if (constraint.name() == name) { diff --git a/tensorflow/compiler/tf2xla/tf2xla_util.h b/tensorflow/compiler/tf2xla/tf2xla_util.h index 95cf589f0b..1232ed8c67 100644 --- a/tensorflow/compiler/tf2xla/tf2xla_util.h +++ b/tensorflow/compiler/tf2xla/tf2xla_util.h @@ -55,7 +55,7 @@ string TensorIdToString(const tf2xla::TensorId& id); Status SetNodeShardingFromNeighbors(Node* n, bool out_edges); // Add an allowed data type to the AttrConstraint with the given name. -void AddDtypeToKernalDefConstraint(absl::string_view name, DataType dtype, +void AddDtypeToKernelDefConstraint(absl::string_view name, DataType dtype, KernelDef* kdef); // Returns the next random seed to use for seeding xla rng. diff --git a/tensorflow/compiler/tf2xla/xla_cpu_backend.cc b/tensorflow/compiler/tf2xla/xla_cpu_backend.cc index bc44301d40..9bb785842d 100644 --- a/tensorflow/compiler/tf2xla/xla_cpu_backend.cc +++ b/tensorflow/compiler/tf2xla/xla_cpu_backend.cc @@ -21,10 +21,10 @@ namespace tensorflow { bool CpuOpFilter(KernelDef* kdef) { if (kdef->op() == "Const") { - AddDtypeToKernalDefConstraint("dtype", DT_STRING, kdef); + AddDtypeToKernelDefConstraint("dtype", DT_STRING, kdef); } if (kdef->op() == "Assert") { - AddDtypeToKernalDefConstraint("T", DT_STRING, kdef); + AddDtypeToKernelDefConstraint("T", DT_STRING, kdef); } return true; } diff --git a/tensorflow/compiler/tf2xla/xla_gpu_backend.cc b/tensorflow/compiler/tf2xla/xla_gpu_backend.cc index 1398e9ee53..5e8006b8d8 100644 --- a/tensorflow/compiler/tf2xla/xla_gpu_backend.cc +++ b/tensorflow/compiler/tf2xla/xla_gpu_backend.cc @@ -21,10 +21,10 @@ namespace tensorflow { bool GpuOpFilter(KernelDef* kdef) { if (kdef->op() == "Const") { - AddDtypeToKernalDefConstraint("dtype", DT_STRING, kdef); + AddDtypeToKernelDefConstraint("dtype", DT_STRING, kdef); } if (kdef->op() == "Assert") { - AddDtypeToKernalDefConstraint("T", DT_STRING, kdef); + AddDtypeToKernelDefConstraint("T", DT_STRING, kdef); } return true; } -- GitLab From 0a9f327e2de9522e58480c7420567f6e78f0b6b8 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 14:05:56 -0700 Subject: [PATCH 0223/1825] Improve performance of MutableLiteralBase::PopulateR1. PiperOrigin-RevId: 217583858 --- tensorflow/compiler/xla/literal.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tensorflow/compiler/xla/literal.h b/tensorflow/compiler/xla/literal.h index 3cd3541fe1..e791048b4d 100644 --- a/tensorflow/compiler/xla/literal.h +++ b/tensorflow/compiler/xla/literal.h @@ -979,9 +979,8 @@ inline void MutableLiteralBase::PopulateR1(absl::Span values) { CHECK_EQ(ShapeUtil::ElementsIn(shape()), values.size()); CHECK_EQ(shape().element_type(), primitive_util::NativeToPrimitiveType()); - for (int64 i = 0; i < values.size(); ++i) { - Set({i}, values[i]); - } + auto data_span = data(); + std::copy(values.begin(), values.end(), data_span.begin()); } template -- GitLab From ef3629757a33e5f6da4d137a7773b4a2883b1265 Mon Sep 17 00:00:00 2001 From: Allen Lavoie Date: Wed, 17 Oct 2018 14:13:58 -0700 Subject: [PATCH 0224/1825] Give graph functions input placeholder names based on the Python function Input placeholders have uniquified names based on the function argument names, and also record the "original" Python name of the argument in an attribute (which should survive a round trip through export and import). Generally only naturally unique names will work in signatures for export. Output structure is already recorded in concrete functions, and together this should be enough information to create SignatureDefs in a SavedModel given just a concrete function. The same sort of plumbing will be necessary for exporting PolymorphicFunctions for re-import into Python. PiperOrigin-RevId: 217585301 --- tensorflow/python/eager/BUILD | 2 + tensorflow/python/eager/def_function.py | 51 ++++- tensorflow/python/eager/function.py | 122 +++++++++--- tensorflow/python/eager/function_test.py | 188 ++++++++++++++++++ .../framework/function_def_to_graph_test.py | 2 +- 5 files changed, 330 insertions(+), 35 deletions(-) diff --git a/tensorflow/python/eager/BUILD b/tensorflow/python/eager/BUILD index 751e8c402e..e79aa5d756 100644 --- a/tensorflow/python/eager/BUILD +++ b/tensorflow/python/eager/BUILD @@ -148,9 +148,11 @@ cuda_py_test( additional_deps = [ ":backprop", ":context", + ":def_function", ":function", ":tape", ":test", + "@absl_py//absl/testing:parameterized", "//tensorflow/python:test_ops", "//tensorflow/python:clip_ops", "//tensorflow/python:init_ops", diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 022c8685a8..53894ce011 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -19,7 +19,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import functools import weakref from tensorflow.python.eager import context @@ -32,6 +31,7 @@ from tensorflow.python.ops import variable_scope from tensorflow.python.ops import variables from tensorflow.python.training.checkpointable import base as checkpointable from tensorflow.python.util import nest +from tensorflow.python.util import tf_decorator class UnliftedInitializerVariable(resource_variable_ops.ResourceVariable): @@ -177,7 +177,8 @@ def _defun_with_scope(scope, fn, input_signature): with variable_scope.variable_creator_scope(scope): return fn(*args, **kwds) - return function_lib.defun(wrapped_fn, input_signature=input_signature) + return function_lib.defun(tf_decorator.make_decorator(fn, wrapped_fn), + input_signature=input_signature) # TODO(apassos) there should be an easier way to call a concrete defun. @@ -204,11 +205,13 @@ class PolymorphicFunction(object): def __init__(self, python_function, - input_signature=None,): + name, + input_signature=None): """Initializes a polymorphic function. Args: python_function: the function to be wrapped. + name: the name given to it. input_signature: a possibly nested sequence of `TensorSpec` objects specifying the input signature of this function. If `None`, a separate function is instantiated for each inferred input signature. @@ -222,6 +225,7 @@ class PolymorphicFunction(object): self._created_variables = None self._stateful_fn = None self._descriptor_cache = weakref.WeakKeyDictionary() + self._name = name def _initialize(self, args, kwds): """Initializes, on the first call.""" @@ -236,6 +240,7 @@ class PolymorphicFunction(object): self._stateful_fn = _defun_with_scope( variable_capturing_scope, self._python_function, self._input_signature) + self._stateful_fn._name = self._name # pylint: disable=protected-access # Force the definition of the function for these arguments self._concrete_stateful_fn = self._stateful_fn.get_concrete_function( @@ -249,6 +254,7 @@ class PolymorphicFunction(object): self._stateless_fn = _defun_with_scope( invalid_creator_scope, self._python_function, self._input_signature) + self._stateless_fn._name = self._name # pylint: disable=protected-access def __call__(self, *args, **kwds): """Calls the graph function.""" @@ -326,7 +332,7 @@ class PolymorphicFunction(object): elif self._stateful_fn is not None: # In this case we have not created variables on the first call. So we can # run the first trace but we should fail if variables are created. - concrete = self._first_trace.get_concrete_function(*args, **kwargs) + concrete = self._stateful_fn.get_concrete_function(*args, **kwargs) if self._created_variables: raise ValueError("Creating variables on a non-first call to a function" " decorated with tf.function.") @@ -353,12 +359,39 @@ class PolymorphicFunction(object): # tf.function. Keeps a cache to avoid retracing the function every time the # descriptor is accessed. if instance not in self._descriptor_cache: - self._descriptor_cache[instance] = PolymorphicFunction( - functools.partial(self.python_function, instance), - self._input_signature) + if instance is None: + return self + self._descriptor_cache[instance] = ( + function_lib.class_method_to_instance_method(self, instance)) return self._descriptor_cache[instance] -def function(fn=None, input_signature=None): +def function(func=None, input_signature=None): """Defines a function as per the "functions, not sessions" document.""" - return PolymorphicFunction(fn, input_signature) + if input_signature is not None: + function_lib.validate_signature(input_signature) + + def decorated(inner_function): + try: + name = inner_function.__name__ + except AttributeError: + name = "function" + return tf_decorator.make_decorator( + inner_function, + PolymorphicFunction( + inner_function, + name, + input_signature=input_signature)) + + # This code path is for the `foo = tf.function(foo, ...)` use case + if func is not None: + return decorated(func) + + # This code path is for the + # + # @tf.function(...) + # def foo(...): + # ... + # + # use case, which is equivalent to `foo = tf.function(...)(foo)` + return decorated diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 6d34cffdf6..b22db21ebe 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -24,6 +24,7 @@ import functools import re import sys import threading +import types as types_lib import weakref import numpy as np @@ -853,14 +854,56 @@ class Function(object): return ret -def _get_defun_inputs_from_args(args): - """Maps python function args to graph-construction inputs.""" - function_inputs = [ - graph_placeholder(arg.dtype, arg.shape) - if isinstance(arg, (ops.Tensor, tensor_spec.TensorSpec)) - else arg for arg in nest.flatten(args) - ] - return nest.pack_sequence_as(args, function_inputs) +def _get_defun_inputs(flat_args, names, structure): + """Maps python function args to graph-construction inputs. + + Args: + flat_args: A flat list of user-specified arguments. + names: A list of strings with user-specified argument names, same length as + `flat_args`. May be `None`, in which case a generic name is used. + structure: The original argument list or dictionary. + + Returns: + Placeholders with the same structure as `structure`. + """ + function_inputs = [] + if names is None: + names = [None] * len(flat_args) + for arg_value, name in zip(flat_args, names): + for arg in nest.flatten(arg_value): + if isinstance(arg, (ops.Tensor, tensor_spec.TensorSpec)): + if isinstance(arg, tensor_spec.TensorSpec) and arg.name: + requested_name = arg.name + else: + requested_name = name + placeholder = graph_placeholder( + arg.dtype, arg.shape, + name=requested_name) + if name is not None: + # Record the requested/user-specified name in case it's different than + # the uniquified name, for validation when exporting signatures. + placeholder.op._set_attr( # pylint: disable=protected-access + "_user_specified_name", + attr_value_pb2.AttrValue(s=compat.as_bytes(requested_name))) + function_inputs.append(placeholder) + else: + function_inputs.append(arg) + return nest.pack_sequence_as(structure, function_inputs) + + +def _get_defun_inputs_from_kwargs(kwargs): + """Maps Python function keyword args to graph-construction inputs.""" + if kwargs: + names, flat_args = zip(*sorted(kwargs.items())) + else: + names = [] + flat_args = [] + return _get_defun_inputs(flat_args, names, structure=kwargs) + + +def _get_defun_inputs_from_args(args, names): + """Maps Python function positional args to graph-construction inputs.""" + return _get_defun_inputs(args, names, structure=args) def check_mutation(n1, n2): @@ -886,7 +929,8 @@ def func_graph_from_py_func(name, signature=None, func_graph=None, experimental_autograph=False, - add_control_dependencies=True): + add_control_dependencies=True, + arg_names=None): """Returns a `FuncGraph` generated from `python_func`. Args: @@ -908,6 +952,8 @@ def func_graph_from_py_func(name, add_control_dependencies: If True, automatically adds control dependencies to ensure program order matches execution order and stateful ops always execute. + arg_names: Optional list of argument names, used to give input placeholders + recognizable names. Returns: A FuncGraph. @@ -932,8 +978,8 @@ def func_graph_from_py_func(name, args = signature kwargs = {} - func_args = _get_defun_inputs_from_args(args) - func_kwargs = _get_defun_inputs_from_args(kwargs) + func_args = _get_defun_inputs_from_args(args, arg_names) + func_kwargs = _get_defun_inputs_from_kwargs(kwargs) # Note: `nest.flatten` sorts by keys, as does `_deterministic_dict_values`. # Variables to help check whether mutation happens in calling the function @@ -1095,6 +1141,8 @@ class PolymorphicFunction(object): # A cache mapping from argument name to index, for canonicalizing # arguments that are called in a keyword-like fashion. self._args_to_indices = {arg: i for i, arg in enumerate(args)} + self._arg_names = args + self._vararg_name = fullargspec.varargs # A cache mapping from arg index to default value, for canonicalization. offset = len(args) - len(fullargspec.defaults or []) self._arg_indices_to_default_values = { @@ -1170,19 +1218,10 @@ class PolymorphicFunction(object): # we construct an instance-specific polymorphic function # that uses a weak reference to the instance (so that the instance will # be correctly gc'd). - def make_partial_py_func(py_func, weak_instance): - return lambda *args, **kwargs: py_func(weak_instance(), *args, **kwargs) - weak_instance = weakref.ref(instance) - instance_func = PolymorphicFunction( - make_partial_py_func(self.python_function, weak_instance), - name=self._name) - - # And we wrap the function with tf_decorator so inspection works correctly - wrapped_instance_func = tf_decorator.make_decorator( - self.python_function, instance_func) # And finally add the wrapped function to the description cache - self._descriptor_cache[instance] = wrapped_instance_func + self._descriptor_cache[instance] = class_method_to_instance_method( + self, instance) # Return the cached polymorphic function for the instance return self._descriptor_cache[instance] @@ -1346,6 +1385,13 @@ class PolymorphicFunction(object): "must be hashable.") if graph_function is None: + if self._input_signature is None: + arglen = len(args) + else: + arglen = len(self._input_signature) + arg_names = ( + self._arg_names[:arglen] + + [self._vararg_name] * (arglen - len(self._arg_names))) graph_function = Function( func_graph_from_py_func( self._name, @@ -1353,7 +1399,8 @@ class PolymorphicFunction(object): args, kwargs, self._input_signature, - experimental_autograph=self._experimental_autograph), + experimental_autograph=self._experimental_autograph, + arg_names=arg_names), self._function_attributes) self._function_cache[cache_key] = graph_function return graph_function, [ @@ -1410,7 +1457,7 @@ def register(func, *args, **kwargs): return concrete_func -def _validate_signature(signature): +def validate_signature(signature): if any(not isinstance(arg, tensor_spec.TensorSpec) for arg in nest.flatten(signature)): raise TypeError("Invalid input_signature %s; input_signature must be " @@ -1772,7 +1819,7 @@ def defun_with_attributes(func=None, graph. """ if input_signature is not None: - _validate_signature(input_signature) + validate_signature(input_signature) # TODO(apassos): deal with captured global state. Deal with control flow. def decorated(function): @@ -2025,6 +2072,31 @@ class AutomaticControlDependencies(object): if o._control_flow_context is r.op._control_flow_context]) # pylint: disable=protected-access +def class_method_to_instance_method(original_function, instance): + """Constructs a new PolymorphicFunction with `self` bound.""" + def make_partial_py_func(py_func, weak_instance): + return lambda *args, **kwargs: py_func(weak_instance(), *args, **kwargs) + weak_instance = weakref.ref(instance) + + # pylint: disable=protected-access + # We make a dummy MethodType object to generate the correct bound method + # signature. The actual call is to a function with a weak reference to + # `instance`. + instance_func = type(original_function)( + tf_decorator.make_decorator( + types_lib.MethodType(original_function.python_function, False), + make_partial_py_func(original_function.python_function, + weak_instance)), + name=original_function._name, + input_signature=original_function._input_signature) + # pylint: enable=protected-access + + # And we wrap the function with tf_decorator so inspection works correctly + wrapped_instance_func = tf_decorator.make_decorator( + original_function.python_function, instance_func) + return wrapped_instance_func + + def automatic_control_dependencies(f): """Wraps f to automatically insert control dependencies. diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 2d75b2c246..31b7aaad18 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -23,6 +23,7 @@ from multiprocessing.pool import ThreadPool import sys import weakref +from absl.testing import parameterized import numpy from tensorflow.core.protobuf import config_pb2 @@ -30,6 +31,7 @@ from tensorflow.core.protobuf import rewriter_config_pb2 from tensorflow.python import keras 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 @@ -2806,6 +2808,192 @@ class AutomaticControlDependenciesTest(test.TestCase): del m self.assertEqual([], list(weak_variables)) + +@parameterized.named_parameters( + dict(testcase_name='Defun', function_decorator=function.defun), + dict(testcase_name='DefFunction', function_decorator=def_function.function)) +class ArgumentNamingTests(test.TestCase, parameterized.TestCase): + """Tests for recognizable export signatures from concrete functions.""" + + def testBasic(self, function_decorator): + @function_decorator + def fn(a, b): + return a + b, a * b + # Call the function to make def_function happy + fn(array_ops.ones([]), array_ops.ones([])) + + fn_op = fn.get_concrete_function( + tensor_spec.TensorSpec(shape=(None,), dtype=dtypes.float32), + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32)) + self.assertEqual( + ['a', 'b'], + [inp.op.name for inp in fn_op.inputs]) + self.assertEqual( + [b'a', b'b'], + [inp.op.get_attr('_user_specified_name') for inp in fn_op.inputs]) + self.assertEqual(2, len(fn_op.graph.structured_outputs)) + + def testDictReturned(self, function_decorator): + @function_decorator + def fn(x, z=(1., 2.), y=3.): + z1, z2 = z + return {'alpha': x + y + z1, 'beta': x * y + z2} + # Call the function to make def_function happy + fn(array_ops.ones([])) + + fn_op = fn.get_concrete_function( + x=tensor_spec.TensorSpec(shape=(None,), dtype=dtypes.float32), + y=tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32)) + self.assertEqual( + ['x', 'y'], + [inp.op.name for inp in fn_op.inputs]) + self.assertEqual( + [b'x', b'y'], + [inp.op.get_attr('_user_specified_name') for inp in fn_op.inputs]) + self.assertEqual({'alpha', 'beta'}, + set(fn_op.graph.structured_outputs.keys())) + + fn_op2 = fn.get_concrete_function( + z=(tensor_spec.TensorSpec(shape=(None,), dtype=dtypes.float32), + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32)), + y=tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32, name='custom'), + x=4.) + self.assertEqual( + ['z', 'z_1', 'custom'], + [inp.op.name for inp in fn_op2.inputs]) + self.assertEqual( + [b'z', b'z', b'custom'], + [inp.op.get_attr('_user_specified_name') for inp in fn_op2.inputs]) + + fn_op3 = fn.get_concrete_function( + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32, name='custom'), + z=(tensor_spec.TensorSpec(shape=(None,), dtype=dtypes.float32), + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32)), + y=tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32, name='custom')) + self.assertEqual( + ['custom', 'z', 'z_1', 'custom_1'], + [inp.op.name for inp in fn_op3.inputs]) + self.assertEqual( + [b'custom', b'z', b'z', b'custom'], + [inp.op.get_attr('_user_specified_name') for inp in fn_op3.inputs]) + + def testMethod(self, function_decorator): + class HasMethod(object): + + @function_decorator + def method(self, x): + return x + + has_method = HasMethod() + # Call the function to make def_function happy + HasMethod.method(has_method, array_ops.ones([])) + class_op = HasMethod.method.get_concrete_function( + has_method, tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32)) + self.assertEqual( + ['x'], + [inp.op.name for inp in class_op.inputs]) + self.assertEqual( + [b'x'], + [inp.op.get_attr('_user_specified_name') for inp in class_op.inputs]) + # Call the function to make def_function happy + has_method.method(array_ops.ones([])) + method_op = has_method.method.get_concrete_function( + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32)) + self.assertEqual( + ['x'], + [inp.op.name for inp in method_op.inputs]) + self.assertEqual( + [b'x'], + [inp.op.get_attr('_user_specified_name') for inp in method_op.inputs]) + # TODO(allenl): It should be possible to override names when exporting. Do + # TensorSpec names need to go in cache keys? Or maybe get_concrete_function + # should always retrace? + self.skipTest('Not working') + method_op = has_method.method.get_concrete_function( + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32, name='y')) + self.assertEqual( + ['y'], + [inp.op.name for inp in method_op.inputs]) + self.assertEqual( + [b'y'], + [inp.op.get_attr('_user_specified_name') for inp in method_op.inputs]) + + def testMethodSignature(self, function_decorator): + + class HasMethod(object): + + @function_decorator( + input_signature=(tensor_spec.TensorSpec( + shape=None, dtype=dtypes.float64, name='y'),)) + def method(self, x): + hash(self) # No weak proxies passed as `self` + return x + + has_method = HasMethod() + # Call the function to make def_function happy + has_method.method(array_ops.ones([], dtype=dtypes.float64)) + method_op = has_method.method.get_concrete_function() + self.assertEqual( + ['y'], + [inp.op.name for inp in method_op.inputs]) + self.assertEqual( + [b'y'], + [inp.op.get_attr('_user_specified_name') for inp in method_op.inputs]) + method_op2 = has_method.method.get_concrete_function() + self.assertEqual( + ['y'], + [inp.op.name for inp in method_op2.inputs]) + self.assertEqual( + [b'y'], + [inp.op.get_attr('_user_specified_name') for inp in method_op2.inputs]) + + def testVariadic(self, function_decorator): + @function_decorator + def variadic_fn(x, *args, **kwargs): + return x + math_ops.add_n(list(args) + list(kwargs.values())) + + # Call the function to make def_function happy + variadic_fn(array_ops.ones([]), array_ops.ones([])) + variadic_op = variadic_fn.get_concrete_function( + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32), + tensor_spec.TensorSpec(shape=None, dtype=dtypes.float32, name='y'), + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32), + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32), + z=tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32), + zz=tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32, name='cust')) + self.assertEqual( + ['x', 'y', 'args', 'args_1', 'z', 'cust'], + [inp.op.name for inp in variadic_op.inputs]) + self.assertEqual( + [b'x', b'y', b'args', b'args', b'z', b'cust'], + [inp.op.get_attr('_user_specified_name') + for inp in variadic_op.inputs]) + + def testVariadicInputSignature(self, function_decorator): + @function_decorator( + input_signature=( + tensor_spec.TensorSpec(shape=None, dtype=dtypes.float32), + tensor_spec.TensorSpec(shape=None, dtype=dtypes.float32, name='y'), + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32), + tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32), + )) + def variadic_fn(x, *args): + return x + math_ops.add_n(list(args)) + + # Call the function to make def_function happy + variadic_fn(array_ops.ones([]), array_ops.ones([]), + array_ops.ones([]), array_ops.ones([])) + variadic_op = variadic_fn.get_concrete_function() + self.assertIn(b'variadic_fn', variadic_op.name) + self.assertEqual( + ['x', 'y', 'args', 'args_1'], + [inp.op.name for inp in variadic_op.inputs]) + self.assertEqual( + [b'x', b'y', b'args', b'args'], + [inp.op.get_attr('_user_specified_name') + for inp in variadic_op.inputs]) + + if __name__ == '__main__': ops.enable_eager_execution( config=config_pb2.ConfigProto(device_count={'CPU': 4})) diff --git a/tensorflow/python/framework/function_def_to_graph_test.py b/tensorflow/python/framework/function_def_to_graph_test.py index e013fb6e4d..b2ef64f873 100644 --- a/tensorflow/python/framework/function_def_to_graph_test.py +++ b/tensorflow/python/framework/function_def_to_graph_test.py @@ -238,7 +238,7 @@ class FunctionDefToGraphDefTest(test.TestCase): op = func_graph.get_operation_by_name("y") self.assertEqual(len(op.control_inputs), 2) self.assertEqual(op.control_inputs[0].name, "x") - self.assertEqual(op.control_inputs[1].name, "placeholder") + self.assertEqual(op.control_inputs[1].name, "inp") if __name__ == "__main__": -- GitLab From 7ee78faca9c113b7dd187c8677c1894cd0ea3b26 Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Wed, 17 Oct 2018 14:15:21 -0700 Subject: [PATCH 0225/1825] Make TRT create_inference_graph() able to accept a RewriterConfig provided by user, so user can control which optimizers to run and how to run them. Existing default may not be always the best, and it makes testing/debugging much harder since the behavior of default settings can change over time. PiperOrigin-RevId: 217585527 --- .../contrib/tensorrt/python/trt_convert.py | 32 +++++++++++----- .../tensorrt/python/trt_convert_test.py | 2 + .../test/tf_trt_integration_test_base.py | 37 +++++++++++-------- 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/tensorflow/contrib/tensorrt/python/trt_convert.py b/tensorflow/contrib/tensorrt/python/trt_convert.py index 369e73b5a6..99890d910e 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert.py @@ -63,7 +63,8 @@ class TrtPrecisionMode(object): return [TrtPrecisionMode.FP32, TrtPrecisionMode.FP16, TrtPrecisionMode.INT8] -def tensorrt_rewriter_config(max_batch_size=1, +def tensorrt_rewriter_config(rewriter_config=None, + max_batch_size=1, max_workspace_size_bytes=2 << 20, precision_mode=TrtPrecisionMode.FP32, minimum_segment_size=3, @@ -73,6 +74,8 @@ def tensorrt_rewriter_config(max_batch_size=1, """Returns a RewriterConfig proto for TRT transformation. Args: + rewriter_config: a RewriterConfig proto to append the TensorRTOptimizer to. + If None, it will create one with default settings. max_batch_size: max size for the input batch max_workspace_size_bytes: the maximum GPU temporary memory which the TRT engine can use at execution time. This corresponds to the 'workspaceSize' @@ -97,18 +100,24 @@ def tensorrt_rewriter_config(max_batch_size=1, A RewriterConfig proto which sets a TensorRTOptimizer to run Grappler. Raises: - TypeError: if the provided precision mode is invalid. - ValueError: if len(cached_engine_batch_sizes) exceed maximum_cached_engines. + TypeError: if any of the parameters are of unexpected type. + ValueError: if any of the parameters are of unexpected value. """ + if rewriter_config is not None and not isinstance( + rewriter_config, rewriter_config_pb2.RewriterConfig): + raise TypeError("rewriter_config should be a RewriterConfig proto.") + + if rewriter_config is None: + rewriter_config = rewriter_config_pb2.RewriterConfig() + rewriter_config.optimizers.extend(["constfold", "layout"]) + if precision_mode.upper() not in TrtPrecisionMode.supported_precision_modes(): raise ValueError(("precision mode '{}' is not supported." "It should be one of {}").format( precision_mode, TrtPrecisionMode.supported_precision_modes)) - rewriter_cfg = rewriter_config_pb2.RewriterConfig() - rewriter_cfg.optimizers.extend(["constfold", "layout"]) - optimizer = rewriter_cfg.custom_optimizers.add() + optimizer = rewriter_config.custom_optimizers.add() optimizer.name = "TensorRTOptimizer" optimizer.parameter_map["minimum_segment_size"].i = minimum_segment_size optimizer.parameter_map["max_batch_size"].i = max_batch_size @@ -125,7 +134,7 @@ def tensorrt_rewriter_config(max_batch_size=1, "maximum_cached_engines items.") optimizer.parameter_map["cached_engine_batches"].list.i.extend( cached_engine_batch_sizes) - return rewriter_cfg + return rewriter_config def create_inference_graph(input_graph_def, @@ -137,6 +146,7 @@ def create_inference_graph(input_graph_def, is_dynamic_op=False, maximum_cached_engines=1, cached_engine_batch_sizes=None, + rewriter_config=None, input_saved_model_dir=None, input_saved_model_tags=None, output_saved_model_dir=None, @@ -168,6 +178,8 @@ def create_inference_graph(input_graph_def, use this list to determine the batch sizes of the cached engines, instead of making the decision on the fly. This is useful when we know the most common batch size(s) the application is going to generate. + rewriter_config: a RewriterConfig proto to append the TensorRTOptimizer to. + If None, it will create one with default settings. input_saved_model_dir: the directory to load the SavedModel which contains the input graph to transforms. Used only when input_graph_def is None. input_saved_model_tags: list of tags to load the SavedModel. @@ -307,14 +319,14 @@ def create_inference_graph(input_graph_def, output_collection) # Create RewriterConfig. - rewriter_cfg = tensorrt_rewriter_config( - max_batch_size, max_workspace_size_bytes, precision_mode, + rewriter_config = 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) # Run Grappler. transformed_graph_def = tf_optimizer.OptimizeGraph( - rewriter_cfg, grappler_meta_graph_def, graph_id=b"tf_graph") + rewriter_config, grappler_meta_graph_def, graph_id=b"tf_graph") # Optionally write the transformed graphdef as SavedModel. if output_saved_model_dir is not None: diff --git a/tensorflow/contrib/tensorrt/python/trt_convert_test.py b/tensorflow/contrib/tensorrt/python/trt_convert_test.py index 52cb0bd9f9..530adafcb3 100644 --- a/tensorflow/contrib/tensorrt/python/trt_convert_test.py +++ b/tensorflow/contrib/tensorrt/python/trt_convert_test.py @@ -49,6 +49,7 @@ class TrtConvertTest(test_util.TensorFlowTestCase): def testTensorrtRewriterConfig(self): """Test case for trt_convert.tensorrt_rewriter_config().""" rewriter_cfg = trt_convert.tensorrt_rewriter_config( + rewriter_config=None, max_batch_size=128, max_workspace_size_bytes=1234, precision_mode="INT8", @@ -56,6 +57,7 @@ class TrtConvertTest(test_util.TensorFlowTestCase): is_dynamic_op=True, maximum_cached_engines=2, cached_engine_batch_sizes=[1, 128]) + self.assertEqual(["constfold", "layout"], rewriter_cfg.optimizers) trt_optimizer = None for optimizer in rewriter_cfg.custom_optimizers: if optimizer.name == "TensorRTOptimizer": 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 4f935a7665..a725d0651c 100644 --- a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py +++ b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py @@ -49,7 +49,7 @@ RunParams = namedtuple( 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" + "cached_engine_batch_sizes", "rewriter_config" ]) PRECISION_MODES = ["FP32", "FP16", "INT8"] @@ -138,7 +138,8 @@ 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_batch_sizes=None, + rewriter_config=None) def ShouldRunTest(self, run_params): """Whether to run the test.""" @@ -200,12 +201,15 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): def _GetConfigProto(self, run_params, graph_state): """Get config proto based on specific settings.""" if graph_state != GraphState.ORIGINAL and run_params.use_optimizer: - trt_params = self.GetConversionParams(run_params) + conversion_params = self.GetConversionParams(run_params) rewriter_cfg = trt_convert.tensorrt_rewriter_config( - trt_params.max_batch_size, trt_params.max_workspace_size_bytes, - trt_params.precision_mode, trt_params.minimum_segment_size, - trt_params.is_dynamic_op, trt_params.maximum_cached_engines, - trt_params.cached_engine_batch_sizes) + conversion_params.rewriter_config, conversion_params.max_batch_size, + conversion_params.max_workspace_size_bytes, + conversion_params.precision_mode, + conversion_params.minimum_segment_size, + conversion_params.is_dynamic_op, + conversion_params.maximum_cached_engines, + conversion_params.cached_engine_batch_sizes) graph_options = config_pb2.GraphOptions(rewrite_options=rewriter_cfg) else: @@ -285,18 +289,19 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): def _GetTrtGraphDef(self, run_params, gdef): """Return trt converted graphdef.""" params = self._GetParamsCached() - trt_params = self.GetConversionParams(run_params) - logging.info(trt_params) + conversion_params = self.GetConversionParams(run_params) + logging.info(conversion_params) return trt_convert.create_inference_graph( input_graph_def=gdef, outputs=params.input_names + params.output_names, - max_batch_size=trt_params.max_batch_size, - max_workspace_size_bytes=trt_params.max_workspace_size_bytes, - precision_mode=trt_params.precision_mode, - minimum_segment_size=trt_params.minimum_segment_size, - is_dynamic_op=trt_params.is_dynamic_op, - maximum_cached_engines=trt_params.maximum_cached_engines, - cached_engine_batch_sizes=trt_params.cached_engine_batch_sizes) + max_batch_size=conversion_params.max_batch_size, + max_workspace_size_bytes=conversion_params.max_workspace_size_bytes, + precision_mode=conversion_params.precision_mode, + 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, + rewriter_config=conversion_params.rewriter_config) def _WriteGraph(self, run_params, gdef, graph_state): if graph_state == GraphState.ORIGINAL: -- GitLab From a4c2c0b09ef214fa29c9a095ebcf9db244d9ffe6 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 14:19:07 -0700 Subject: [PATCH 0226/1825] Fix README error for micro build. PiperOrigin-RevId: 217586261 --- tensorflow/contrib/lite/experimental/micro/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/lite/experimental/micro/README.md b/tensorflow/contrib/lite/experimental/micro/README.md index fc539db62e..e03703f496 100644 --- a/tensorflow/contrib/lite/experimental/micro/README.md +++ b/tensorflow/contrib/lite/experimental/micro/README.md @@ -90,7 +90,8 @@ To understand what's happening here, try running the same depthwise convolution ``` tensorflow/contrib/lite/experimental/micro/testing/test_bluepill_binary.sh \ -tensorflow/contrib/lite/experimental/micro/tools/make/gen/bluepill_cortex-m3/bin/tensorflow/contrib/lite/experimental/micro/kernels/depthwise_conv_test +tensorflow/contrib/lite/experimental/micro/tools/make/gen/bluepill_cortex-m3/bin/tensorflow/contrib/lite/experimental/micro/kernels/depthwise_conv_test \ +'~~~ALL TESTS PASSED~~~' ``` -- GitLab From dc5bf64549c605e59b646379ac88e7d5d5e8f3b9 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 14:20:54 -0700 Subject: [PATCH 0227/1825] Internal change. PiperOrigin-RevId: 217586654 --- .../contrib/lite/kernels/fully_connected.cc | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/tensorflow/contrib/lite/kernels/fully_connected.cc b/tensorflow/contrib/lite/kernels/fully_connected.cc index f6d2f76dbe..cac556db33 100644 --- a/tensorflow/contrib/lite/kernels/fully_connected.cc +++ b/tensorflow/contrib/lite/kernels/fully_connected.cc @@ -55,7 +55,7 @@ struct OpData { int32_t output_activation_min; int32_t output_activation_max; // The index of the temporary tensor where the quantized inputs are cached. - int input_quantized_index; + int scratch_tensor_index; }; constexpr int kInputTensor = 0; @@ -63,7 +63,6 @@ constexpr int kWeightsTensor = 1; constexpr int kBiasTensor = 2; constexpr int kOutputTensor = 0; constexpr int kShuffledInputWorkspaceTensor = 1; -constexpr int kScratchBufferTensor = 1; void* Init(TfLiteContext* context, const char* buffer, size_t length) { // This is a builtin op, so we don't use the contents in 'buffer', if any. @@ -71,7 +70,8 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { // Eval(). gemm_support::IncrementUsageCounter(context); auto* op_data = new OpData(); - context->AddTensors(context, 1, &op_data->input_quantized_index); + context->AddTensors(context, /*tensors_to_add=*/2, + &op_data->scratch_tensor_index); return op_data; } @@ -134,11 +134,10 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { // buffer to store the intermediate quantized values. if (input->type == kTfLiteFloat32 && filter->type == kTfLiteUInt8) { TfLiteIntArrayFree(node->temporaries); - node->temporaries = TfLiteIntArrayCreate(1); - node->temporaries->data[0] = data->input_quantized_index; + node->temporaries = TfLiteIntArrayCreate(2); + node->temporaries->data[0] = data->scratch_tensor_index; - TfLiteTensor* input_quantized = - &context->tensors[node->temporaries->data[0]]; + TfLiteTensor* input_quantized = GetTemporary(context, node, /*index=*/0); input_quantized->type = kTfLiteUInt8; input_quantized->allocation_type = kTfLiteArenaRw; @@ -148,6 +147,17 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, input_quantized, input_quantized_size)); } + node->temporaries->data[1] = data->scratch_tensor_index + 1; + TfLiteTensor* scaling_factors = GetTemporary(context, node, /*index=*/1); + scaling_factors->type = kTfLiteFloat32; + scaling_factors->allocation_type = kTfLiteArenaRw; + int scaling_dims[1] = {batch_size}; + if (!TfLiteIntArrayEqualsArray(scaling_factors->dims, 1, scaling_dims)) { + TfLiteIntArray* scaling_factors_size = TfLiteIntArrayCreate(1); + scaling_factors_size->data[0] = batch_size; + TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, scaling_factors, + scaling_factors_size)); + } } // Resize output. @@ -192,13 +202,11 @@ TfLiteStatus EvalPie(TfLiteContext* context, TfLiteNode* node, return kTfLiteOk; } -TfLiteStatus EvalPieQuantized(TfLiteContext* context, TfLiteNode* node, - TfLiteFullyConnectedParams* params, OpData* data, - const TfLiteTensor* input, - const TfLiteTensor* filter, - const TfLiteTensor* bias, - TfLiteTensor* input_quantized, - TfLiteTensor* output) { +TfLiteStatus EvalHybrid(TfLiteContext* context, TfLiteNode* node, + TfLiteFullyConnectedParams* params, OpData* data, + const TfLiteTensor* input, const TfLiteTensor* filter, + const TfLiteTensor* bias, TfLiteTensor* input_quantized, + TfLiteTensor* scaling_factors, TfLiteTensor* output) { // Check the types for this hybrid Op. TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32); TF_LITE_ENSURE_EQ(context, filter->type, kTfLiteUInt8); @@ -231,31 +239,29 @@ TfLiteStatus EvalPieQuantized(TfLiteContext* context, TfLiteNode* node, } // Quantize input from float to uint8 + quantization params (scaling factor). - float min, max; - float* scaling_factors = new float[batch_size]; + float unused_min, unused_max; + float* scaling_factors_ptr = scaling_factors->data.f; + int8_t* quant_data = reinterpret_cast(input_quantized->data.uint8); // Quantize each batch independently. for (int b = 0; b < batch_size; ++b) { const int offset = b * input_size; - tensor_utils::SymmetricQuantizeFloats( - input->data.f + offset, input_size, - reinterpret_cast(input_quantized->data.uint8) + offset, &min, - &max, &scaling_factors[b]); + tensor_utils::SymmetricQuantizeFloats(input->data.f + offset, input_size, + quant_data + offset, &unused_min, + &unused_max, &scaling_factors_ptr[b]); // Incorporate scaling of the filter. - scaling_factors[b] *= filter->params.scale; + scaling_factors_ptr[b] *= filter->params.scale; } // Compute output += weight * quantized_input tensor_utils::MatrixBatchVectorMultiplyAccumulate( reinterpret_cast(filter->data.uint8), num_units, input_size, - reinterpret_cast(input_quantized->data.uint8), scaling_factors, - batch_size, output->data.f, /*result_stride=*/1); + quant_data, scaling_factors_ptr, batch_size, output->data.f, + /*result_stride=*/1); // Apply activation function to floats. tensor_utils::ApplyActivationToVector(output->data.f, batch_size * num_units, params->activation, output->data.f); - delete[] scaling_factors; - return kTfLiteOk; } @@ -314,10 +320,10 @@ TfLiteStatus EvalQuantized(TfLiteContext* context, TfLiteNode* node, } } else if (kernel_type == kPie && input->type == kTfLiteFloat32) { // Pie currently only supports quantized models and float inputs/outputs. - TfLiteTensor* input_quantized = - &context->tensors[node->temporaries->data[0]]; - return EvalPieQuantized(context, node, params, data, input, filter, bias, - input_quantized, output); + TfLiteTensor* input_quantized = GetTemporary(context, node, /*index=*/0); + TfLiteTensor* scaling_factors = GetTemporary(context, node, /*index=*/1); + return EvalHybrid(context, node, params, data, input, filter, bias, + input_quantized, scaling_factors, output); } else { switch (output->type) { case kTfLiteUInt8: -- GitLab From 7378227401af2f1acb5a7f9e3e62fe0f813726c3 Mon Sep 17 00:00:00 2001 From: Blake Hechtman Date: Wed, 17 Oct 2018 15:29:30 -0700 Subject: [PATCH 0228/1825] [XLA] Fix Scatter HLO cost analysis. This is at least more right than the elementwise cost estimation and should be right for simple scatters. It may not work for all combinations of scatter dimension numbers. PiperOrigin-RevId: 217600097 --- .../compiler/xla/service/hlo_cost_analysis.cc | 13 +++++++++- .../xla/service/hlo_cost_analysis_test.cc | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/service/hlo_cost_analysis.cc b/tensorflow/compiler/xla/service/hlo_cost_analysis.cc index 23ab4cda93..108aeea097 100644 --- a/tensorflow/compiler/xla/service/hlo_cost_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_cost_analysis.cc @@ -674,7 +674,18 @@ Status HloCostAnalysis::HandleGather(const HloInstruction* gather) { } Status HloCostAnalysis::HandleScatter(const HloInstruction* scatter) { - // TODO(b/32945756): Compute the properties of the sub-computation. + current_properties_[kBytesAccessedKey] = + GetShapeSize(scatter->operand(2)->shape()) * 2 + + GetShapeSize(scatter->operand(1)->shape()); + const int64 element_count = + ShapeUtil::ElementsIn(scatter->operand(2)->shape()); + TF_ASSIGN_OR_RETURN(const Properties sub_properties, + ProcessSubcomputation(scatter->to_apply())); + for (const auto& property : sub_properties) { + if (property.first != kBytesAccessedKey) { + current_properties_[property.first] = property.second * element_count; + } + } return Status::OK(); } diff --git a/tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc b/tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc index 802cdfc9e4..9acee892d5 100644 --- a/tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc +++ b/tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc @@ -581,5 +581,31 @@ TEST_F(HloCostAnalysisTest, Gather) { EXPECT_EQ(analysis.bytes_accessed(), 56); } +TEST_F(HloCostAnalysisTest, Scatter) { + // Test the analysis on a scatter. + XlaBuilder builder("scatter"); + Shape operand_shape = ShapeUtil::MakeShape(F32, {3, 3}); + Shape indices_shape = ShapeUtil::MakeShape(S32, {2}); + Shape values_shape = ShapeUtil::MakeShape(F32, {2, 3}); + + auto operand = Parameter(&builder, 0, operand_shape, "operand"); + auto indices = Parameter(&builder, 1, indices_shape, "indices"); + auto values = Parameter(&builder, 2, values_shape, "values"); + ScatterDimensionNumbers dim_numbers; + dim_numbers.set_index_vector_dim(1); + dim_numbers.add_update_window_dims(1); + dim_numbers.add_inserted_window_dims(0); + dim_numbers.add_scatter_dims_to_operand_dims(0); + Scatter(operand, indices, values, add_, dim_numbers); + + auto hlo_module = BuildHloGraph(&builder); + + // Run HLO cost analysis. + HloCostAnalysis analysis(ShapeSize); + ASSERT_IS_OK( + hlo_module->entry_computation()->root_instruction()->Accept(&analysis)); + + EXPECT_EQ(analysis.bytes_accessed(), 4 * (2 + 2 * (2 * 3))); +} } // namespace } // namespace xla -- GitLab From 8bd9c36c86b78b14c1138e0377d50fb1b4cc5c51 Mon Sep 17 00:00:00 2001 From: Cong Xu Date: Wed, 10 Oct 2018 12:24:30 -0700 Subject: [PATCH 0229/1825] [Intel MKL] Add scripts for creating allreduce based Tensorflow k8s deployment Create allreduce based distributed Tensorflow k8s deployment for testing Intel optimized Tensorflow horovod dockerfile - tensorflow/tools/docker/Dockerfile.devel-mkl-horovod - tensorflow/tools/docker/Dockerfile.mkl-horovod Signed-off-by: Cong Xu --- tensorflow/tools/dist_test/README.md | 38 ++- .../k8s_deploy_tensorflow.sh | 254 ++++++++++++++++++ .../scripts_allreduce/k8s_generate_yaml.py | 94 +++++++ .../k8s_generate_yaml_lib.py | 225 ++++++++++++++++ 4 files changed, 609 insertions(+), 2 deletions(-) create mode 100755 tensorflow/tools/dist_test/scripts_allreduce/k8s_deploy_tensorflow.sh create mode 100644 tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml.py create mode 100644 tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml_lib.py diff --git a/tensorflow/tools/dist_test/README.md b/tensorflow/tools/dist_test/README.md index f8ed74aaf7..6e34a3ce04 100644 --- a/tensorflow/tools/dist_test/README.md +++ b/tensorflow/tools/dist_test/README.md @@ -1,6 +1,6 @@ # Testing Distributed Runtime in TensorFlow -This folder containers tools and test suites for the GRPC-based distributed -runtime in TensorFlow. +This folder contains tools and test suites for GRPC-based and Allreduce-based +distributed runtimes in TensorFlow. There are three general modes of testing: @@ -122,3 +122,37 @@ servers. For example: See [Kubernetes kubectl documentation](http://kubernetes.io/docs/user-guide/kubectl-overview/) for more details. + +**Create allreduce-based Tensorflow k8s deployment** + +The allreduce-based Tensorflow, Horovod, is an open source distributed deep +learning framework for TensorFlow, detailed information can be found in +https://arxiv.org/pdf/1802.05799.pdf. + +The script "scripts_allreduce/k8s_deploy_tensorflow.sh" can be used to create +or delete an allreduce-based Tensorflow k8s deployment with specified number of +containers. + +Create a deployment containing a number of containers and enable passwordless +ssh between the containers (optional: enable host network mode with --hostnet +and --port ): + + scripts_allreduce/k8s_deploy_tensorflow.sh \ + --num_containers \ + --image \ + --deployment \ + --config_map + +Delete a deployment and config_map in k8s cluster: + + scripts_allreduce/k8s_deploy_tensorflow.sh \ + --deployment \ + --config_map \ + --delete + +Upload file or directory to all the containers of a deployment: + + scripts_allreduce/k8s_deploy_tensorflow.sh \ + --cp --src \ + --dest \ + --deployment diff --git a/tensorflow/tools/dist_test/scripts_allreduce/k8s_deploy_tensorflow.sh b/tensorflow/tools/dist_test/scripts_allreduce/k8s_deploy_tensorflow.sh new file mode 100755 index 0000000000..2f83c36fad --- /dev/null +++ b/tensorflow/tools/dist_test/scripts_allreduce/k8s_deploy_tensorflow.sh @@ -0,0 +1,254 @@ +#!/usr/bin/env bash +# 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. +# ============================================================================== + +function usage { + script_name=$0 + echo "Usage:" + echo " $script_name [--image docker_image] [--num_containers num_of_containers]" + echo " [--deployment deployment_name] [--config_map config_map]" + echo " [--cp] [--src local_src_dir] [--dest container_dest_dir]" + echo " [--port container_ssh_port] [--hostnet] [--shared_volume]" + echo " [--delete] [--help]" + echo "" + echo " Parameters:" + echo " image: docker image used to create container." + echo " num_containers: number of containers that will be launched." + echo " deployment: deployment name. (default: k8s-ml-deployment)" + echo " config_map: config map name. (default: k8s-config-map)" + echo " cp: upload file to all containers. (src and dest must" + echo " be provided along with cp option)" + echo " src: path to local source file. (used for cp option)" + echo " dest: path to destination in container. (used for cp option)" + echo " port: ssh port in container. Set ssh port (other than 22)" + echo " when host network mode is enabled" + echo " hostnet: enable host network mode. (default: disable)" + echo " shared_volume: mount shared volume. (default: disable)" + echo " delete: delete deployment and configmap." + echo " (default: k8s-ml-deployment and k8s-config-map)" + echo " help: print usage." +} + +# Create temporary directory +TMP_DIR=$(mktemp -d) + +# Temporary k8s yaml file +YAML_TMP_FILE="${TMP_DIR}/k8s_ml.yaml" + +# Temporary hostfile +HOST_FILE="${TMP_DIR}/hostfile" + +# Docker image and number of containers +DOCKER_IMAGE="" +NUM_CONTAINERS=0 + +# Default ssh port +SSH_PORT=22 + +# Default config map +CONFIG_MAP="k8s-config-map" + +# Default Deployment +DEPLOYMENT="k8s-ml-deployment" + +# Used for uploading file to all docker containers +CP=0 +SRC="" +DEST="" + +# Python script to generate yaml file for k8s TensorFlow cluster +CUR_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +K8S_GEN_ALLREDUCE_TF_YAML="${CUR_SCRIPT_DIR}/k8s_generate_yaml.py" + +# Create or delete tensorflow cluster +# DELETE=0: Create cluster +# DELETE=1: Delete cluster +DELETE=0 + +# Used to enable host network mode to achieve best performance +# USE_HOSTNET=0: Flannel network mode +# USE_HOSTNET=1: Host network mode +USE_HOSTNET=0 + +# Used to mount shared volume +USE_SHARED_VOLUME=0 + +if [[ $# -lt 1 ]]; then + echo "Error: illegal number of parameters" + usage + exit 1 +fi + +while [[ $# -ge 1 ]]; do + key="$1" + case $key in + --image) + DOCKER_IMAGE="$2" + shift + ;; + --num_containers) + NUM_CONTAINERS="$2" + shift + ;; + --config_map) + CONFIG_MAP="$2" + shift + ;; + --deployment) + DEPLOYMENT="$2" + shift + ;; + --cp) + CP=1 + ;; + --src) + SRC="$2" + shift + ;; + --dest) + DEST="$2" + shift + ;; + --port) + SSH_PORT="$2" + shift + ;; + --hostnet) + USE_HOSTNET=1 + ;; + --shared_volume) + USE_SHARED_VOLUME=1 + ;; + --delete) + DELETE=1 + ;; + --help) + usage + exit 0 + ;; + *) + echo "Unknown option: $key" + usage + exit 1 + ;; + esac + shift +done + +function generate_yaml_file { + if [[ ! -f ${K8S_GEN_ALLREDUCE_TF_YAML} ]]; then + echo "Error: can not find yaml-generating script ${K8S_GEN_ALLREDUCE_TF_YAML}" + exit 1 + fi + + echo "" + echo "Generating k8s cluster yaml config file with the following settings" + echo " Docker image: ${DOCKER_IMAGE}" + echo " Number of containers: ${NUM_CONTAINERS}" + echo " Config map: ${CONFIG_MAP}" + echo " Deployment: ${DEPLOYMENT}" + + if [[ $USE_HOSTNET -eq 1 ]]; then + echo " Host network mode: True" + echo " Container ssh port: ${SSH_PORT}" + fi + + python ${K8S_GEN_ALLREDUCE_TF_YAML} \ + --docker_image ${DOCKER_IMAGE} \ + --num_containers ${NUM_CONTAINERS} \ + --config_map ${CONFIG_MAP} \ + --deployment ${DEPLOYMENT} \ + --ssh_port ${SSH_PORT} \ + --use_hostnet ${USE_HOSTNET} \ + --use_shared_volume ${USE_SHARED_VOLUME} \ + > ${YAML_TMP_FILE} +} + +# Note: this function remove the yaml file to make sure that the key automatically +# generated inside the container is not reused in other deployment +function remove_yaml_file { + rm -rf ${YAML_TMP_FILE} +} + +function upload_file_to_all_containers { + ${KUBECTL_BIN} get pods | grep ${DEPLOYMENT} \ + | awk '{print $1}' | \ + while read line; + do + echo "Uploading $1 to $line:$2" + ${KUBECTL_BIN} cp $1 $line:$2 + done +} + +function generate_container_hostfile { + # This line assumes that --output=wide prints the IP addresses + # in the 6th column + ${KUBECTL_BIN} get pods --output=wide | grep ${DEPLOYMENT} \ + | awk '{print $6}' > ${HOST_FILE} + + echo "" + echo "Containers hostfile locates at ${HOST_FILE}" +} + +function launch_container { + generate_yaml_file + echo "" + echo "Launching k8s cluster..." + ${KUBECTL_BIN} create -f ${YAML_TMP_FILE} + generate_container_hostfile + remove_yaml_file +} + +function delete_deployment_configmap { + ${KUBECTL_BIN} delete deployment ${DEPLOYMENT} + ${KUBECTL_BIN} delete configmap ${CONFIG_MAP} +} + +# Check kubectl binary +KUBECTL_BIN=kubectl +if [[ ! -x "$(command -v ${KUBECTL_BIN})" ]]; then + echo 'Error: cannot find kubectl binary' + exit 1 +fi + +if [[ $DELETE -eq 1 ]]; then + echo "Deleting deployment ${DEPLOYMENT} and config map ${CONFIG_MAP}..." + delete_deployment_configmap +elif [[ $CP -eq 1 || -n "$SRC" || -n "$DEST" ]] ; then + if [[ "$CP" -eq 1 && -n "$SRC" && -n "$DEST" ]]; then + upload_file_to_all_containers $SRC $DEST + else + echo "Error: all cp, src and dest are required to upload file to container" + exit 1 + fi +else + if [[ -z "$DOCKER_IMAGE" ]]; then + echo "Error: docker image is missing" + exit 1 + fi + + if [[ "$NUM_CONTAINERS" -le 0 ]]; then + echo "Error: illegal number of containers" + exit 1 + fi + + if [[ $USE_HOSTNET -eq 1 && $SSH_PORT -eq 22 ]]; then + echo "Error: please set container ssh port with --port (other than 22)" \ + "when host network mode is enabled" + exit 1 + fi + + launch_container +fi diff --git a/tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml.py b/tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml.py new file mode 100644 index 0000000000..dda067085b --- /dev/null +++ b/tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# 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. +# ============================================================================== + +"""Generates YAML configuration file for allreduce-based distributed TensorFlow. + +The workers will be run in a Kubernetes (k8s) container cluster. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import sys + +import k8s_generate_yaml_lib + +# Note: It is intentional that we do not import tensorflow in this script. The +# machine that launches a TensorFlow k8s cluster does not have to have the +# Python package of TensorFlow installed on it. + + +DEFAULT_DOCKER_IMAGE = 'tensorflow/tensorflow:latest-devel' +DEFAULT_PORT = 22 + +DEFAULT_CONFIG_MAP = 'k8s-config-map' +DEFAULT_DEPLOYMENT = 'k8s-ml-deployment' + + +def main(): + """Do arg parsing.""" + parser = argparse.ArgumentParser() + parser.add_argument('--docker_image', + type=str, + default=DEFAULT_DOCKER_IMAGE, + help='Override default docker image for the TensorFlow') + parser.add_argument('--num_containers', + type=int, + default=0, + help='How many docker containers to launch') + parser.add_argument('--config_map', + type=str, + default=DEFAULT_CONFIG_MAP, + help='Override default config map') + parser.add_argument('--deployment', + type=str, + default=DEFAULT_DEPLOYMENT, + help='Override default deployment') + parser.add_argument('--ssh_port', + type=int, + default=DEFAULT_PORT, + help='Override default ssh port (Default: %d)' + % DEFAULT_PORT) + parser.add_argument('--use_hostnet', + type=int, + default=0, + help='Used to enable host network mode (Default: 0)') + parser.add_argument('--use_shared_volume', + type=int, + default=0, + help='Used to mount shared volume (Default: 0)') + args = parser.parse_args() + + if args.num_containers <= 0: + sys.stderr.write('--num_containers must be greater than 0; received %d\n' + % args.num_containers) + sys.exit(1) + + # Generate contents of yaml config + yaml_config = k8s_generate_yaml_lib.GenerateConfig( + args.docker_image, + args.num_containers, + args.config_map, + args.deployment, + args.ssh_port, + args.use_hostnet, + args.use_shared_volume) + print(yaml_config) # pylint: disable=superfluous-parens + + +if __name__ == '__main__': + main() 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 new file mode 100644 index 0000000000..3aadcaec01 --- /dev/null +++ b/tensorflow/tools/dist_test/scripts_allreduce/k8s_generate_yaml_lib.py @@ -0,0 +1,225 @@ +#!/usr/bin/python +# 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. +# ============================================================================== + +"""Generates YAML configuration file for allreduce-based distributed TensorFlow. + +The workers will be run in a Kubernetes (k8s) container cluster. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from Crypto.PublicKey import RSA + +# Note: It is intentional that we do not import tensorflow in this script. The +# machine that launches a TensorFlow k8s cluster does not have to have the +# Python package of TensorFlow installed on it. + +CONFIG_MAP = ( + """apiVersion: v1 +kind: ConfigMap +metadata: + name: {config_map} +data: + privatekey: |+ + {private_key} + + publickey: |+ + {public_key} + + start: |+ + mkdir /root/.ssh + mkdir /var/run/sshd + cp /tmp/configs/* /root/.ssh + cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys + chmod 600 -R /root/.ssh + {change_ssh_port} + /usr/bin/ssh-keygen -A + /usr/sbin/sshd -De + + sshconfig: |+ + Host * + Port {port} + StrictHostKeyChecking no + +""") + +DEPLOYMENT = ( + """apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: {deployment} + labels: + app: k8s-ml +spec: + replicas: {num_containers} + selector: + matchLabels: + app: k8s-ml + template: + metadata: + labels: + app: k8s-ml + spec: {hostnet} + securityContext: + runAsUser: 0 + containers: + - name: ml + image: {docker_image} + command: + - /bin/bash + - -x + - /tmp/scripts/start.sh + ports: + - containerPort: {port} + env: [{env_vars}] + securityContext: + privileged: true + volumeMounts: {volume_mounts} + - name: dshm + mountPath: /dev/shm + - name: sshkeys + mountPath: /tmp/configs + - name: scripts + mountPath: /tmp/scripts + volumes: {volumes} + - name: dshm + emptyDir: + medium: Memory + - name: sshkeys + configMap: + name: {config_map} + items: + - key: publickey + path: id_rsa.pub + - key: privatekey + path: id_rsa + - key: sshconfig + path: config + - name: scripts + configMap: + name: {config_map} + items: + - key: start + path: start.sh +""") +_ENV_VAR_TEMPLATE = '{name: "%s", value: "%s"}' + +def GenerateConfig(docker_image, + num_containers, + config_map, + deployment, + port, + use_hostnet, + use_shared_volume, + env_vars=None): + """Generate configuration strings. + + Args: + docker_image: docker image to use. + num_containers: number of containers. + config_map: config map. + deployment: deployment. + port: ssh port. + use_hostnet: Used to enable host network mode. + use_shared_volume: Used to mount shared volume. + env_vars: dictionary of environment variables to set. + + Returns: + Kubernetes yaml config. + """ + + if env_vars is None: + env_vars = {} + env_str = ', '.join([_ENV_VAR_TEMPLATE % (name, value) + for name, value in env_vars.items()]) + + private_key, public_key = generate_RSA(2048) + + CHANGE_SSH_PORT = get_change_ssh_port(use_hostnet, port) + + config = CONFIG_MAP.format( + port=port, + config_map=config_map, + private_key=private_key, + public_key=public_key, + change_ssh_port=CHANGE_SSH_PORT, + env_vars=env_str) + config += '---\n\n' + + HOST_NET = get_hostnet(use_hostnet) + VOLUME_MOUNTS = get_volume_mounts(use_shared_volume) + VOLUMES = get_volumes(use_shared_volume) + + config += DEPLOYMENT.format( + deployment=deployment, + num_containers=num_containers, + docker_image=docker_image, + port=port, + config_map=config_map, + hostnet=HOST_NET, + volume_mounts=VOLUME_MOUNTS, + volumes=VOLUMES, + env_vars=env_str) + + return config + +def generate_RSA(bits=2048, exponent=65537): + key = RSA.generate(bits, e=exponent) + pubkey = key.publickey() + + private_key = key.exportKey('PEM') + public_key = pubkey.exportKey('OpenSSH') + + # Format private_key in yaml file + space_before = " " * 4 + private_key_split = private_key.split('\n') + private_key = ''.join(("" if index == 0 else space_before) + line.strip() \ + + ('\n' if index != len(private_key_split) - 1 else "") \ + for index, line in enumerate(private_key_split)) + + return private_key, public_key + +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 "" + +def get_hostnet(use_hostnet): + if use_hostnet == 1: + return """ + hostNetwork: true + hostIPC: true""" + + return "" + +def get_volume_mounts(use_shared_volume): + if use_shared_volume == 1: + return """ + - name: shared + mountPath: /shared""" + + return "" + +def get_volumes(use_shared_volume): + if use_shared_volume == 1: + return """ + - name: shared + hostPath: + path: /shared""" + + return "" -- GitLab From 41953d1c9f83d4f27b02dd0c4de91b2ee67ebc90 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 15:39:26 -0700 Subject: [PATCH 0230/1825] internal cleanup PiperOrigin-RevId: 217601811 --- tensorflow/core/platform/default/build_config.bzl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tensorflow/core/platform/default/build_config.bzl b/tensorflow/core/platform/default/build_config.bzl index c9c89d066e..78a2048eaa 100644 --- a/tensorflow/core/platform/default/build_config.bzl +++ b/tensorflow/core/platform/default/build_config.bzl @@ -322,11 +322,9 @@ def tf_proto_library_cc( dart_api_version = 2, java_api_version = 2, py_api_version = 2, - js_api_version = 2, js_codegen = "jspb", default_header = False): js_codegen = js_codegen # unused argument - js_api_version = js_api_version # unused argument native.filegroup( name = name + "_proto_srcs", srcs = srcs + tf_deps(protodeps, "_proto_srcs"), @@ -442,12 +440,11 @@ def tf_proto_library( j2objc_api_version = 1, java_api_version = 2, py_api_version = 2, - js_api_version = 2, js_codegen = "jspb", provide_cc_alias = False, default_header = False): """Make a proto library, possibly depending on other proto libraries.""" - _ignore = (js_api_version, js_codegen, provide_cc_alias) + _ignore = (js_codegen, provide_cc_alias) tf_proto_library_cc( name = name, -- GitLab From 61873aa973b394ac882c89f1bddc9f6fcf2ec09e Mon Sep 17 00:00:00 2001 From: Alexandre Passos Date: Wed, 17 Oct 2018 15:51:57 -0700 Subject: [PATCH 0231/1825] Enables step_stats collection for ops inside partitionedcall. PiperOrigin-RevId: 217603935 --- .../core/common_runtime/eager/execute.cc | 22 +++++++++++------- .../core/common_runtime/eager/execute.h | 3 ++- .../core/common_runtime/eager/execute_node.h | 6 +++-- .../common_runtime/eager/kernel_and_device.cc | 15 ++++++++---- .../common_runtime/eager/kernel_and_device.h | 6 +++-- .../eager/kernel_and_device_test.cc | 2 +- tensorflow/python/eager/function_test.py | 23 +++++++++++++++++++ 7 files changed, 58 insertions(+), 19 deletions(-) diff --git a/tensorflow/core/common_runtime/eager/execute.cc b/tensorflow/core/common_runtime/eager/execute.cc index c29a767d23..51402c12f0 100644 --- a/tensorflow/core/common_runtime/eager/execute.cc +++ b/tensorflow/core/common_runtime/eager/execute.cc @@ -324,7 +324,9 @@ Status EagerLocalExecute(EagerOperation* op, ctx->ShouldStoreMetadata() ? ctx->RunMetadataProto() : nullptr); if (!status.ok()) return status; std::unique_ptr maybe_stats; + StepStats* maybe_step_stats = nullptr; if (ctx->ShouldStoreMetadata()) { + maybe_step_stats = ctx->RunMetadataProto()->mutable_step_stats(); int64 now_nanos = Env::Default()->NowNanos(); maybe_stats.reset(new NodeExecStats); maybe_stats->set_node_name(op->Name()); @@ -345,15 +347,16 @@ Status EagerLocalExecute(EagerOperation* op, for (int i = 0; i < *num_retvals; ++i) { (*retvals)[i] = new TensorHandle(id, output_dtypes[i], ctx); } - EagerNode* node = - new ExecuteNode(id, ctx, op->Device(), op->Inputs(), kernel, - maybe_stats.release(), output_dtypes, *retvals); + EagerNode* node = new ExecuteNode( + id, ctx, op->Device(), op->Inputs(), kernel, maybe_stats.release(), + maybe_step_stats, output_dtypes, *retvals); ctx->ExecutorAdd(node); } else { // Execute checks if retvals[i] is nullptr or not to figure if it needs to // allocate it. - status = EagerExecute(ctx, op->Device(), op->Inputs(), kernel, - maybe_stats.get(), retvals->data(), *num_retvals); + status = + EagerExecute(ctx, op->Device(), op->Inputs(), kernel, maybe_stats.get(), + maybe_step_stats, retvals->data(), *num_retvals); } return status; @@ -707,7 +710,8 @@ Status EagerExecute(EagerOperation* op, Status EagerExecute(EagerContext* ctx, Device* device, const gtl::InlinedVector& op_inputs, KernelAndDevice* kernel, NodeExecStats* maybe_stats, - TensorHandle** retvals, int num_retvals) { + StepStats* maybe_step_stats, TensorHandle** retvals, + int num_retvals) { if (device == nullptr) { // TODO(apassos) debug how the assignment below might return a different // device from the one requested above. @@ -728,9 +732,11 @@ Status EagerExecute(EagerContext* ctx, Device* device, // TODO(agarwal): change Run to take vector of handles ? ScopedStepContainer* container = ctx->StepContainer(); if (container == nullptr) { - TF_RETURN_IF_ERROR(kernel->Run(&inputs, &outputs, maybe_stats)); + TF_RETURN_IF_ERROR( + kernel->Run(&inputs, &outputs, maybe_stats, maybe_step_stats)); } else { - TF_RETURN_IF_ERROR(kernel->Run(container, &inputs, &outputs, maybe_stats)); + TF_RETURN_IF_ERROR(kernel->Run(container, &inputs, &outputs, maybe_stats, + maybe_step_stats)); } if (maybe_stats != nullptr) { int64 nanos = Env::Default()->NowNanos(); diff --git a/tensorflow/core/common_runtime/eager/execute.h b/tensorflow/core/common_runtime/eager/execute.h index f4f84980fb..0e997bdfa9 100644 --- a/tensorflow/core/common_runtime/eager/execute.h +++ b/tensorflow/core/common_runtime/eager/execute.h @@ -46,7 +46,8 @@ Status EagerExecute( Status EagerExecute(EagerContext* ctx, Device* device, const gtl::InlinedVector& op_inputs, KernelAndDevice* kernel, NodeExecStats* maybe_stats, - TensorHandle** retvals, int num_retvals); + StepStats* maybe_step_stats, TensorHandle** retvals, + int num_retvals); // Low-level utility to copy a tensor handle from one device to another. Status EagerCopyToDevice(TensorHandle* h, EagerContext* ctx, diff --git a/tensorflow/core/common_runtime/eager/execute_node.h b/tensorflow/core/common_runtime/eager/execute_node.h index 93018dd969..18b1892f5d 100644 --- a/tensorflow/core/common_runtime/eager/execute_node.h +++ b/tensorflow/core/common_runtime/eager/execute_node.h @@ -34,7 +34,7 @@ class ExecuteNode : public EagerNode { ExecuteNode(uint64 id, EagerContext* ctx, Device* op_device, const tensorflow::gtl::InlinedVector& inputs, KernelAndDevice* kernel, NodeExecStats* maybe_stats, - const DataTypeVector& output_dtypes, + StepStats* maybe_step_stats, const DataTypeVector& output_dtypes, const tensorflow::gtl::InlinedVector& retvals) : EagerNode(id), ctx_(ctx), @@ -42,6 +42,7 @@ class ExecuteNode : public EagerNode { inputs_(inputs), kernel_(kernel), maybe_stats_(maybe_stats), + maybe_step_stats_(maybe_step_stats), retvals_(retvals) { for (auto handle : inputs_) { handle->Ref(); @@ -63,7 +64,7 @@ class ExecuteNode : public EagerNode { tensorflow::Status Run() override { const Status status = EagerExecute(ctx_, op_device_, inputs_, kernel_, maybe_stats_.get(), - retvals_.begin(), retvals_.size()); + maybe_step_stats_, retvals_.begin(), retvals_.size()); if (status.ok()) { return status; } else { @@ -80,6 +81,7 @@ class ExecuteNode : public EagerNode { tensorflow::gtl::InlinedVector inputs_; tensorflow::KernelAndDevice* kernel_; std::unique_ptr maybe_stats_; + StepStats* maybe_step_stats_; tensorflow::gtl::InlinedVector retvals_; }; diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.cc b/tensorflow/core/common_runtime/eager/kernel_and_device.cc index 83d8425477..0adfcd7697 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.cc +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.cc @@ -17,6 +17,7 @@ limitations under the License. #include "tensorflow/core/common_runtime/device_factory.h" #include "tensorflow/core/common_runtime/rendezvous_mgr.h" +#include "tensorflow/core/common_runtime/step_stats_collector.h" #include "tensorflow/core/framework/allocator.h" #include "tensorflow/core/framework/node_def.pb.h" #include "tensorflow/core/framework/resource_mgr.h" @@ -46,18 +47,18 @@ Status KernelAndDevice::Init(const NodeDef& ndef, FunctionLibraryRuntime* flib, } Status KernelAndDevice::Run(std::vector* inputs, - std::vector* outputs, - NodeExecStats* stats) { + std::vector* outputs, NodeExecStats* stats, + StepStats* step_stats) { ScopedStepContainer step_container(0, [this](const string& name) { device_->resource_manager()->Cleanup(name).IgnoreError(); }); - return this->Run(&step_container, inputs, outputs, stats); + return this->Run(&step_container, inputs, outputs, stats, step_stats); } Status KernelAndDevice::Run(ScopedStepContainer* step_container, std::vector* inputs, - std::vector* outputs, - NodeExecStats* stats) { + std::vector* outputs, NodeExecStats* stats, + StepStats* step_stats) { gtl::InlinedVector input_vector; for (Tensor& t : *inputs) { input_vector.push_back(TensorValue(&t)); @@ -81,8 +82,11 @@ Status KernelAndDevice::Run(ScopedStepContainer* step_container, params.rendezvous = rendez_; params.cancellation_manager = &cm_; params.log_memory = log_memory_; + std::unique_ptr step_stats_collector; if (stats != nullptr) { + step_stats_collector.reset(new StepStatsCollector(step_stats)); params.track_allocations = true; + params.stats_collector = step_stats_collector.get(); } if (runner_ == nullptr) { params.runner = &default_runner_; @@ -132,6 +136,7 @@ Status KernelAndDevice::Run(ScopedStepContainer* step_container, } ms->set_persistent_memory_size(context.persistent_memory_allocated()); + step_stats_collector->Finalize(); } return Status::OK(); } diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.h b/tensorflow/core/common_runtime/eager/kernel_and_device.h index 04151a1171..cbfa0af507 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.h +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.h @@ -36,6 +36,7 @@ namespace tensorflow { // Forward declaration for proto class NodeExecStats so we do not need to // include the proto header class NodeExecStats; +class StepStats; // KernelAndDevice encapsulates an instantiated kernel and the device it is on. // @@ -61,10 +62,11 @@ class KernelAndDevice { // TODO(ashankar): Handle list-valued inputs. Status Run(std::vector* inputs, std::vector* outputs, - NodeExecStats* stats); + NodeExecStats* stats, StepStats* step_stats); Status Run(ScopedStepContainer* step_container, std::vector* inputs, - std::vector* outputs, NodeExecStats* stats); + std::vector* outputs, NodeExecStats* stats, + StepStats* step_stats); const OpKernel* kernel() const { return kernel_.get(); } diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device_test.cc b/tensorflow/core/common_runtime/eager/kernel_and_device_test.cc index da280b2317..fbe0f46f8a 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device_test.cc +++ b/tensorflow/core/common_runtime/eager/kernel_and_device_test.cc @@ -132,7 +132,7 @@ void BM_KernelAndDeviceRun(int iters) { nullptr, &kernel)); tensorflow::testing::StartTiming(); for (int i = 0; i < iters; ++i) { - TF_CHECK_OK(kernel.Run(&inputs, &outputs, nullptr)); + TF_CHECK_OK(kernel.Run(&inputs, &outputs, nullptr, nullptr)); } } BENCHMARK(BM_KernelAndDeviceRun); diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 31b7aaad18..7ed83b54af 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -623,6 +623,29 @@ class FunctionTest(test.TestCase): # Ensure that v is watched again. self.assertAllEqual(backprop.implicit_grad(f)()[0][0], 2.0) + def testRunMetadata(self): + + @function.defun + def f(x): + return x * x + + with ops.device('cpu:0'): + f(constant_op.constant(1.0)) # pre-build the defun + context.enable_run_metadata() + f(constant_op.constant(1.0)) + run_metadata = context.export_run_metadata() + context.disable_run_metadata() + step_stats = run_metadata.step_stats + self.assertGreater(len(step_stats.dev_stats), 0) + cpu_stats = step_stats.dev_stats[0] + self.assertEqual('/job:localhost/replica:0/task:0/device:CPU:0', + cpu_stats.device) + # Testing for at least 2 because the function call should generate at most + # one entry in the step_stats; the ops inside function can generate + # arbitrarily many (placeholders, return identities, etc, might be included + # or not in the future, so shouldn't be tested for exactly. + self.assertGreaterEqual(len(cpu_stats.node_stats), 2) + def testGraphModeCaptureVariable(self): with context.graph_mode(), self.cached_session() as sess: -- GitLab From 75d15a5be2b79574ac0be473c72fa34a88402db9 Mon Sep 17 00:00:00 2001 From: Alan Chiao Date: Wed, 17 Oct 2018 15:58:21 -0700 Subject: [PATCH 0232/1825] Convert unidirectional LSTM in TOCO with time_major=true. PiperOrigin-RevId: 217605153 --- tensorflow/contrib/lite/toco/tflite/operator.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tensorflow/contrib/lite/toco/tflite/operator.cc b/tensorflow/contrib/lite/toco/tflite/operator.cc index 1ee71d4341..a4fce2aa9e 100644 --- a/tensorflow/contrib/lite/toco/tflite/operator.cc +++ b/tensorflow/contrib/lite/toco/tflite/operator.cc @@ -758,7 +758,8 @@ class UnidirectionalSequenceLstm *builder, /*fused_activation_function=*/ ::tflite::ActivationFunctionType_TANH, /*cell_clip=*/0.0, - /*proj_clip=*/0.0); + /*proj_clip=*/0.0, + /*time_major=*/true); } void ReadOptions(const TfLiteOptions& options, -- GitLab From 1868b6cd16e18e371acc4afc454593f7c1d3ff5e Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 16:05:20 -0700 Subject: [PATCH 0233/1825] Parallel_for: add support for IdentityN. PiperOrigin-RevId: 217606272 --- .../python/ops/parallel_for/control_flow_ops_test.py | 8 ++++++++ tensorflow/python/ops/parallel_for/pfor.py | 7 +++++++ 2 files changed, 15 insertions(+) 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 3ad9f9ac58..0a14f3f41a 100644 --- a/tensorflow/python/ops/parallel_for/control_flow_ops_test.py +++ b/tensorflow/python/ops/parallel_for/control_flow_ops_test.py @@ -291,6 +291,14 @@ class ArrayTest(PForTest): 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_strided_slice(self): x = random_ops.random_uniform([3, 3, 4, 4, 2, 2, 2]) diff --git a/tensorflow/python/ops/parallel_for/pfor.py b/tensorflow/python/ops/parallel_for/pfor.py index 83cbe64ff2..d0537d1d5b 100644 --- a/tensorflow/python/ops/parallel_for/pfor.py +++ b/tensorflow/python/ops/parallel_for/pfor.py @@ -1537,6 +1537,13 @@ def _convert_identity(pfor_input, op_type, op_func): return wrap(op_func(*[x.t for x in pfor_input.inputs]), True) +@RegisterPFor("IdentityN") +def _convert_identity_n(pfor_input): + outputs = array_ops.identity_n([x.t for x in pfor_input.inputs]) + return [wrap(out, inp.is_stacked) for out, inp in + zip(outputs, pfor_input.inputs)] + + @RegisterPFor("Reshape") def _convert_reshape(pfor_input): t = pfor_input.stacked_input(0) -- GitLab From 5db6fb0264d0c9454a7867245a70afe868cc751d Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Wed, 17 Oct 2018 16:18:44 -0700 Subject: [PATCH 0234/1825] [TF:XLA] Bump open source abseil revision to 2019e17a520575ab365b2b5134d71068182c70b8 PiperOrigin-RevId: 217608709 --- tensorflow/workspace.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 19a4631d8f..590dd56b9e 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -121,11 +121,11 @@ def tf_workspace(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "com_google_absl", build_file = clean_dep("//third_party:com_google_absl.BUILD"), - sha256 = "4648b8738c059e6061b0dd49c87c139eb5d1e95973d790cf5fcecdbb1d6993ce", - strip_prefix = "abseil-cpp-5b70a8910b2e6fb0ce5193a41873139a126d2f7f", + sha256 = "6bf4a2fb5a153b25c9aea83fa272dc959b0db3be8425f1748a3215605a06d76e", + strip_prefix = "abseil-cpp-2019e17a520575ab365b2b5134d71068182c70b8", urls = [ - "https://mirror.bazel.build/github.com/abseil/abseil-cpp/archive/5b70a8910b2e6fb0ce5193a41873139a126d2f7f.tar.gz", - "https://github.com/abseil/abseil-cpp/archive/5b70a8910b2e6fb0ce5193a41873139a126d2f7f.tar.gz", + "https://mirror.bazel.build/github.com/abseil/abseil-cpp/archive/2019e17a520575ab365b2b5134d71068182c70b8.tar.gz", + "https://github.com/abseil/abseil-cpp/archive/2019e17a520575ab365b2b5134d71068182c70b8.tar.gz", ], ) -- GitLab From 95b8433dcfbcb04a914b6277a74f793c8f175d47 Mon Sep 17 00:00:00 2001 From: Zhenyu Tan Date: Wed, 17 Oct 2018 16:20:54 -0700 Subject: [PATCH 0235/1825] Move implementation details back to contrib optimizer v2. PiperOrigin-RevId: 217609033 --- tensorflow/contrib/optimizer_v2/BUILD | 11 +- tensorflow/contrib/optimizer_v2/adadelta.py | 75 +- tensorflow/contrib/optimizer_v2/adagrad.py | 81 +- tensorflow/contrib/optimizer_v2/adam.py | 130 +- .../optimizer_v2/checkpointable_utils_test.py | 49 +- .../contrib/optimizer_v2/gradient_descent.py | 42 +- tensorflow/contrib/optimizer_v2/momentum.py | 69 +- .../contrib/optimizer_v2/optimizer_v2.py | 1226 ++++++++++++++++- tensorflow/contrib/optimizer_v2/rmsprop.py | 163 ++- 9 files changed, 1738 insertions(+), 108 deletions(-) diff --git a/tensorflow/contrib/optimizer_v2/BUILD b/tensorflow/contrib/optimizer_v2/BUILD index 0700b7c73c..3ba3ee29ec 100644 --- a/tensorflow/contrib/optimizer_v2/BUILD +++ b/tensorflow/contrib/optimizer_v2/BUILD @@ -47,8 +47,15 @@ py_library( ], srcs_version = "PY2AND3", deps = [ - "//tensorflow/python:util", - "//tensorflow/python/keras/optimizer_v2", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:distribute", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:state_ops", + "//tensorflow/python:training", + "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", ], ) diff --git a/tensorflow/contrib/optimizer_v2/adadelta.py b/tensorflow/contrib/optimizer_v2/adadelta.py index 9d73bddd1c..b206f9f61b 100644 --- a/tensorflow/contrib/optimizer_v2/adadelta.py +++ b/tensorflow/contrib/optimizer_v2/adadelta.py @@ -18,21 +18,17 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.keras.optimizer_v2 import adadelta -from tensorflow.python.util import deprecation +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.training import training_ops -class AdadeltaOptimizer(adadelta.Adadelta): +class AdadeltaOptimizer(optimizer_v2.OptimizerV2): """Optimizer that implements the Adadelta algorithm. See [M. D. Zeiler](http://arxiv.org/abs/1212.5701) ([pdf](http://arxiv.org/pdf/1212.5701v1.pdf)) """ - @deprecation.deprecated_args( - "2018-10-01", - "`use_locking = True` is no longer supported and will be ignored.", - ("use_locking", [False])) def __init__(self, learning_rate=0.001, rho=0.95, epsilon=1e-8, use_locking=False, name="Adadelta"): """Construct a new Adadelta optimizer. @@ -52,5 +48,66 @@ class AdadeltaOptimizer(adadelta.Adadelta): name: Optional name prefix for the operations created when applying gradients. Defaults to "Adadelta". """ - super(AdadeltaOptimizer, self).__init__( - learning_rate=learning_rate, rho=rho, epsilon=epsilon, name=name) + super(AdadeltaOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + self._set_hyper("rho", rho) + self._set_hyper("epsilon", epsilon) + + def _create_vars(self, var_list, state): + for v in var_list: + state.zeros_slot(v, "accum") + state.zeros_slot(v, "accum_update") + + def _apply_dense(self, grad, var, state): + accum = state.get_slot(var, "accum") + accum_update = state.get_slot(var, "accum_update") + return training_ops.apply_adadelta( + var, + accum, + accum_update, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("rho", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _resource_apply_dense(self, grad, var, state): + accum = state.get_slot(var, "accum") + accum_update = state.get_slot(var, "accum_update") + return training_ops.resource_apply_adadelta( + var.handle, + accum.handle, + accum_update.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("rho", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _apply_sparse(self, grad, var, state): + accum = state.get_slot(var, "accum") + accum_update = state.get_slot(var, "accum_update") + return training_ops.sparse_apply_adadelta( + var, + accum, + accum_update, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("rho", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad.values, + grad.indices, + use_locking=self._use_locking) + + def _resource_apply_sparse(self, grad, var, indices, state): + accum = state.get_slot(var, "accum") + accum_update = state.get_slot(var, "accum_update") + return training_ops.resource_sparse_apply_adadelta( + var.handle, + accum.handle, + accum_update.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("rho", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + indices, + use_locking=self._use_locking) diff --git a/tensorflow/contrib/optimizer_v2/adagrad.py b/tensorflow/contrib/optimizer_v2/adagrad.py index 716361e29c..346c3fbd2c 100644 --- a/tensorflow/contrib/optimizer_v2/adagrad.py +++ b/tensorflow/contrib/optimizer_v2/adagrad.py @@ -18,11 +18,15 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.keras.optimizer_v2 import adagrad -from tensorflow.python.util import deprecation +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_array_ops +from tensorflow.python.ops import init_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.training import training_ops -class AdagradOptimizer(adagrad.Adagrad): +class AdagradOptimizer(optimizer_v2.OptimizerV2): """Optimizer that implements the Adagrad algorithm. See this [paper](http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf) @@ -30,10 +34,6 @@ class AdagradOptimizer(adagrad.Adagrad): [intro](https://ppasupat.github.io/a9online/uploads/proximal_notes.pdf). """ - @deprecation.deprecated_args( - "2018-10-01", - "`use_locking = True` is no longer supported and will be ignored.", - ("use_locking", [False])) def __init__(self, learning_rate, initial_accumulator_value=0.1, use_locking=False, name="Adagrad"): """Construct a new Adagrad optimizer. @@ -54,7 +54,66 @@ class AdagradOptimizer(adagrad.Adagrad): Raises: ValueError: If the `initial_accumulator_value` is invalid. """ - super(AdagradOptimizer, self).__init__( - learning_rate=learning_rate, - initial_accumulator_value=initial_accumulator_value, - name=name) + if initial_accumulator_value <= 0.0: + raise ValueError("initial_accumulator_value must be positive: %s" % + initial_accumulator_value) + super(AdagradOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + + self._initial_accumulator_value = initial_accumulator_value + + def _create_vars(self, var_list, state): + for v in var_list: + dtype = v.dtype.base_dtype + if v.get_shape().is_fully_defined(): + init = init_ops.constant_initializer( + self._initial_accumulator_value, dtype=dtype) + else: + + def init(v=v, dtype=dtype): + # Use a Tensor instead of initializer if variable does not have + # static shape. + init_constant = gen_array_ops.fill( + array_ops.shape(v), self._initial_accumulator_value) + return math_ops.cast(init_constant, dtype) + + state.create_slot_with_initializer(v, init, v.get_shape(), dtype, + "accumulator") + + def _apply_dense(self, grad, var, state): + acc = state.get_slot(var, "accumulator") + return training_ops.apply_adagrad( + var, + acc, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _resource_apply_dense(self, grad, var, state): + acc = state.get_slot(var, "accumulator") + return training_ops.resource_apply_adagrad( + var.handle, + acc.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _apply_sparse(self, grad, var, state): + acc = state.get_slot(var, "accumulator") + return training_ops.sparse_apply_adagrad( + var, + acc, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad.values, + grad.indices, + use_locking=self._use_locking) + + def _resource_apply_sparse(self, grad, var, indices, state): + acc = state.get_slot(var, "accumulator") + return training_ops.resource_sparse_apply_adagrad( + var.handle, + acc.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + indices, + use_locking=self._use_locking) diff --git a/tensorflow/contrib/optimizer_v2/adam.py b/tensorflow/contrib/optimizer_v2/adam.py index 363e020757..248ffb1f7e 100644 --- a/tensorflow/contrib/optimizer_v2/adam.py +++ b/tensorflow/contrib/optimizer_v2/adam.py @@ -18,21 +18,22 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.keras.optimizer_v2 import adam -from tensorflow.python.util import deprecation +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.framework import ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import state_ops +from tensorflow.python.training import training_ops -class AdamOptimizer(adam.Adam): +class AdamOptimizer(optimizer_v2.OptimizerV2): """Optimizer that implements the Adam algorithm. See [Kingma et al., 2014](http://arxiv.org/abs/1412.6980) ([pdf](http://arxiv.org/pdf/1412.6980.pdf)). """ - @deprecation.deprecated_args( - "2018-10-01", - "`use_locking = True` is no longer supported and will be ignored.", - ("use_locking", [False])) 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. @@ -86,9 +87,112 @@ class AdamOptimizer(adam.Adam): name: Optional name for the operations created when applying gradients. Defaults to "Adam". """ - super(AdamOptimizer, self).__init__( - learning_rate=learning_rate, - beta_1=beta1, - beta_2=beta2, - epsilon=epsilon, - name=name) + super(AdamOptimizer, self).__init__(use_locking, name) + + self._set_hyper("learning_rate", learning_rate) + self._set_hyper("beta1", beta1) + self._set_hyper("beta2", beta2) + self._set_hyper("epsilon", epsilon) + + def _get_beta_accumulators(self, state=None): + if state is None: + state = self._get_per_graph_state() + return (state.get_non_slot("beta1_power"), + state.get_non_slot("beta2_power")) + + def _create_vars(self, var_list, state): + # Non-slot variables end up on the same device(s). + state.create_non_slot( + initial_value=lambda: state.get_hyper("beta1"), name="beta1_power") + state.create_non_slot( + initial_value=lambda: state.get_hyper("beta2"), name="beta2_power") + + # Create slots for the first and second moments. + for v in var_list: + state.zeros_slot(v, "m") + state.zeros_slot(v, "v") + + def _apply_dense(self, grad, var, state): + m = state.get_slot(var, "m") + v = state.get_slot(var, "v") + beta1_power, beta2_power = self._get_beta_accumulators(state) + return training_ops.apply_adam( + var, + m, + v, + math_ops.cast(beta1_power, var.dtype.base_dtype), + math_ops.cast(beta2_power, var.dtype.base_dtype), + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("beta1", var.dtype.base_dtype), + state.get_hyper("beta2", var.dtype.base_dtype), + state.get_hyper("epsilon", var.dtype.base_dtype), + grad, + use_locking=self._use_locking).op + + def _resource_apply_dense(self, grad, var, state): + m = state.get_slot(var, "m") + v = state.get_slot(var, "v") + beta1_power, beta2_power = self._get_beta_accumulators(state) + return training_ops.resource_apply_adam( + var.handle, + m.handle, + v.handle, + math_ops.cast(beta1_power, grad.dtype.base_dtype), + math_ops.cast(beta2_power, grad.dtype.base_dtype), + state.get_hyper("learning_rate", grad.dtype.base_dtype), + state.get_hyper("beta1", grad.dtype.base_dtype), + state.get_hyper("beta2", grad.dtype.base_dtype), + state.get_hyper("epsilon", grad.dtype.base_dtype), + grad, + use_locking=self._use_locking) + + def _apply_sparse_shared(self, grad, var, indices, scatter_add, state): + beta1_power, beta2_power = self._get_beta_accumulators(state) + beta1_power = math_ops.cast(beta1_power, var.dtype.base_dtype) + beta2_power = math_ops.cast(beta2_power, var.dtype.base_dtype) + lr_t = state.get_hyper("learning_rate", var.dtype.base_dtype) + beta1_t = state.get_hyper("beta1", var.dtype.base_dtype) + beta2_t = state.get_hyper("beta2", var.dtype.base_dtype) + epsilon_t = state.get_hyper("epsilon", var.dtype.base_dtype) + lr = (lr_t * math_ops.sqrt(1 - beta2_power) / (1 - beta1_power)) + # m_t = beta1 * m + (1 - beta1) * g_t + m = state.get_slot(var, "m") + m_scaled_g_values = grad * (1 - beta1_t) + m_t = state_ops.assign(m, m * beta1_t, use_locking=self._use_locking) + with ops.control_dependencies([m_t]): + m_t = scatter_add(m, indices, m_scaled_g_values) + # v_t = beta2 * v + (1 - beta2) * (g_t * g_t) + v = state.get_slot(var, "v") + v_scaled_g_values = (grad * grad) * (1 - beta2_t) + v_t = state_ops.assign(v, v * beta2_t, use_locking=self._use_locking) + with ops.control_dependencies([v_t]): + v_t = scatter_add(v, indices, v_scaled_g_values) + v_sqrt = math_ops.sqrt(v_t) + var_update = state_ops.assign_sub( + var, lr * m_t / (v_sqrt + epsilon_t), use_locking=self._use_locking) + return control_flow_ops.group(*[var_update, m_t, v_t]) + + def _apply_sparse(self, grad, var, state): + return self._apply_sparse_shared( + grad.values, var, grad.indices, + lambda x, i, v: state_ops.scatter_add( # pylint: disable=g-long-lambda + x, i, v, use_locking=self._use_locking), + state) + + def _resource_scatter_add(self, x, i, v): + with ops.control_dependencies( + [resource_variable_ops.resource_scatter_add(x.handle, i, v)]): + return x.value() + + def _resource_apply_sparse(self, grad, var, indices, state): + return self._apply_sparse_shared(grad, var, indices, + self._resource_scatter_add, state) + + def _finish(self, state): + # Update the power accumulators. + beta1_power, beta2_power = self._get_beta_accumulators(state) + update_beta1 = beta1_power.assign( + beta1_power * state.get_hyper("beta1"), use_locking=self._use_locking) + update_beta2 = beta2_power.assign( + beta2_power * state.get_hyper("beta2"), use_locking=self._use_locking) + return control_flow_ops.group(update_beta1, update_beta2) diff --git a/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py b/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py index 6362d424ed..ae8a5d243b 100644 --- a/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py +++ b/tensorflow/contrib/optimizer_v2/checkpointable_utils_test.py @@ -24,7 +24,6 @@ import os import six -from tensorflow.contrib.optimizer_v2 import adam from tensorflow.python.client import session as session_lib from tensorflow.python.eager import backprop from tensorflow.python.eager import context @@ -35,6 +34,7 @@ from tensorflow.python.framework import ops from tensorflow.python.framework import test_util from tensorflow.python.keras.engine import training from tensorflow.python.keras.layers import core +from tensorflow.python.keras.optimizer_v2 import adam from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import init_ops from tensorflow.python.ops import resource_variable_ops @@ -98,7 +98,7 @@ class CheckpointingTests(test.TestCase): # A nuisance Model using the same optimizer. Its slot variables should not # go in the checkpoint, since it is never depended on. other_model = MyModel() - optimizer = adam.AdamOptimizer(0.001) + optimizer = adam.Adam(0.001) optimizer_step = training_util.get_or_create_global_step() root_checkpointable = util.Checkpoint( optimizer=optimizer, model=model, optimizer_step=optimizer_step) @@ -208,7 +208,7 @@ class CheckpointingTests(test.TestCase): @test_util.run_in_graph_and_eager_modes def testSaveRestore(self): model = MyModel() - optimizer = adam.AdamOptimizer(0.001) + optimizer = adam.Adam(0.001) root_checkpointable = util.Checkpoint( optimizer=optimizer, model=model) input_value = constant_op.constant([[3.]]) @@ -240,12 +240,12 @@ class CheckpointingTests(test.TestCase): if not context.executing_eagerly(): return # Restore-on-create is only supported when executing eagerly on_create_model = MyModel() - on_create_optimizer = adam.AdamOptimizer( + on_create_optimizer = adam.Adam( 0.001, # Preserve beta_1_power and beta_2_power when appying gradients # so we can test that they've been restored correctly. - beta1=1.0, - beta2=1.0) + beta_1=1.0, + beta_2=1.0) on_create_root = util.Checkpoint( optimizer=on_create_optimizer, model=on_create_model) # Deferred restoration @@ -277,7 +277,7 @@ class CheckpointingTests(test.TestCase): checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") for training_continuation in range(3): model = MyModel() - optimizer = adam.AdamOptimizer(0.001) + optimizer = adam.Adam(0.001) root = util.Checkpoint( optimizer=optimizer, model=model, optimizer_step=training_util.get_or_create_global_step()) @@ -302,7 +302,7 @@ class CheckpointingTests(test.TestCase): for training_continuation in range(3): with ops.Graph().as_default(): model = MyModel() - optimizer = adam.AdamOptimizer(0.001) + optimizer = adam.Adam(0.001) root = util.Checkpoint( optimizer=optimizer, model=model, global_step=training_util.get_or_create_global_step()) @@ -337,10 +337,10 @@ class CheckpointingTests(test.TestCase): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") for training_continuation in range(3): - with ops.Graph().as_default(), self.session( + with ops.Graph().as_default(), self.test_session( graph=ops.get_default_graph()), test_util.device(use_gpu=True): model = MyModel() - optimizer = adam.AdamOptimizer(0.001) + optimizer = adam.Adam(0.001) root = util.Checkpoint( optimizer=optimizer, model=model, global_step=training_util.get_or_create_global_step()) @@ -370,11 +370,11 @@ class CheckpointingTests(test.TestCase): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") for training_continuation in range(3): - with ops.Graph().as_default(), self.session( + with ops.Graph().as_default(), self.test_session( graph=ops.get_default_graph()), test_util.device(use_gpu=True): model = MyModel() # Don't actually train so we can test variable values - optimizer = adam.AdamOptimizer(0.) + optimizer = adam.Adam(0.) root = util.Checkpoint( optimizer=optimizer, model=model, global_step=training_util.get_or_create_global_step()) @@ -423,7 +423,7 @@ class CheckpointingTests(test.TestCase): with context.eager_mode(): model = Model() - optimizer = adam.AdamOptimizer(learning_rate=0.05) + optimizer = adam.Adam(learning_rate=0.05) checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") checkpoint = util.Checkpoint( @@ -444,7 +444,7 @@ class CheckpointingTests(test.TestCase): root = tracking.Checkpointable() root.var = util.add_variable( root, name="var", initializer=0.) - optimizer = adam.AdamOptimizer(0.1) + optimizer = adam.Adam(0.1) if context.executing_eagerly(): optimizer.minimize(root.var.read_value) else: @@ -478,7 +478,7 @@ class CheckpointingTests(test.TestCase): no_slot_status.assert_consumed() no_slot_status.run_restore_ops() self.assertEqual(12., self.evaluate(new_root.var)) - new_root.optimizer = adam.AdamOptimizer(0.1) + new_root.optimizer = adam.Adam(0.1) with self.assertRaisesRegexp(AssertionError, "beta_1_power"): slot_status.assert_consumed() self.assertEqual(12., self.evaluate(new_root.var)) @@ -511,7 +511,7 @@ class CheckpointingTests(test.TestCase): checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") obj = tracking.Checkpointable() obj.var = variable_scope.get_variable(name="v", initializer=0.) - obj.opt = adam.AdamOptimizer(0.1) + obj.opt = adam.Adam(0.1) obj.opt.minimize(obj.var.read_value()) self.evaluate(util.gather_initializers(obj)) saver = util.CheckpointableSaver(obj) @@ -529,7 +529,7 @@ class CheckpointingTests(test.TestCase): checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") obj = tracking.Checkpointable() obj.var = variable_scope.get_variable(name="v", initializer=0.) - obj.opt = adam.AdamOptimizer(0.1) + obj.opt = adam.Adam(0.1) obj.opt.minimize(obj.var.read_value()) self.evaluate(util.gather_initializers(obj)) saver = util.CheckpointableSaver(obj) @@ -543,7 +543,7 @@ class CheckpointingTests(test.TestCase): with context.graph_mode(): checkpoint_directory = self.get_temp_dir() checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") - optimizer = adam.AdamOptimizer(0.001) + optimizer = adam.Adam(0.001) # Construct a model in one graph first_graph = ops.Graph() first_session = session_lib.Session(graph=first_graph) @@ -614,7 +614,7 @@ class TemplateTests(test.TestCase): save_template = template.make_template("s1", _templated) v1_save, _, v2_save = save_template() - optimizer = adam.AdamOptimizer(0.0) + optimizer = adam.Adam(0.0) save_root = util.Checkpoint( my_template=save_template, optimizer=optimizer) optimizer.minimize(v1_save.read_value) @@ -626,7 +626,7 @@ class TemplateTests(test.TestCase): save_path = save_root.save(checkpoint_prefix) load_template = template.make_template("s2", _templated) - load_optimizer = adam.AdamOptimizer(0.0) + load_optimizer = adam.Adam(0.0) load_root = util.Checkpoint( my_template=load_template, optimizer=load_optimizer) status = load_root.restore(save_path) @@ -646,7 +646,7 @@ class CheckpointCompatibilityTests(test.TestCase): def _initialized_model(self): input_value = constant_op.constant([[3.]]) model = MyModel() - optimizer = adam.AdamOptimizer(0.001) + optimizer = adam.Adam(0.001) optimizer_step = training_util.get_or_create_global_step() root_checkpointable = util.Checkpoint( optimizer=optimizer, model=model, optimizer_step=optimizer_step) @@ -688,7 +688,7 @@ class CheckpointCompatibilityTests(test.TestCase): checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") with context.graph_mode(): save_graph = ops.Graph() - with save_graph.as_default(), self.session( + with save_graph.as_default(), self.test_session( graph=save_graph) as session: root = self._initialized_model() name_saver = core_saver.Saver() @@ -733,7 +733,7 @@ class CheckpointCompatibilityTests(test.TestCase): checkpoint_prefix = os.path.join(checkpoint_directory, "ckpt") with context.graph_mode(): save_graph = ops.Graph() - with save_graph.as_default(), self.session( + with save_graph.as_default(), self.test_session( graph=save_graph) as session: root = self._initialized_model() save_path = root.save( @@ -752,8 +752,7 @@ class CheckpointCompatibilityTests(test.TestCase): save_path = root.save(file_prefix=checkpoint_prefix) with context.graph_mode(): save_graph = ops.Graph() - with save_graph.as_default(), self.session( - graph=save_graph): + with save_graph.as_default(), self.test_session(graph=save_graph): root = self._initialized_model() self._set_sentinels(root) root.restore(save_path).assert_consumed().run_restore_ops() diff --git a/tensorflow/contrib/optimizer_v2/gradient_descent.py b/tensorflow/contrib/optimizer_v2/gradient_descent.py index 8bdf408217..d103a55a35 100644 --- a/tensorflow/contrib/optimizer_v2/gradient_descent.py +++ b/tensorflow/contrib/optimizer_v2/gradient_descent.py @@ -13,22 +13,20 @@ # limitations under the License. # ============================================================================== -"""GradientDescent optimizer for TensorFlow.""" +"""Momentum for TensorFlow.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.keras.optimizer_v2 import sgd -from tensorflow.python.util import deprecation +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.framework import ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.training import training_ops -class GradientDescentOptimizer(sgd.SGD): +class GradientDescentOptimizer(optimizer_v2.OptimizerV2): """Optimizer that implements the gradient descent algorithm.""" - @deprecation.deprecated_args( - "2018-10-01", - "`use_locking = True` is no longer supported and will be ignored.", - ("use_locking", [False])) def __init__(self, learning_rate, use_locking=False, name="GradientDescent"): """Construct a new gradient descent optimizer. @@ -43,5 +41,29 @@ class GradientDescentOptimizer(sgd.SGD): name: Optional name prefix for the operations created when applying gradients. Defaults to "GradientDescent". """ - super(GradientDescentOptimizer, self).__init__( - learning_rate=learning_rate, name=name) + super(GradientDescentOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + + def _apply_dense(self, grad, var, state): + return training_ops.apply_gradient_descent( + var, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + use_locking=self._use_locking).op + + def _resource_apply_dense(self, grad, handle, state): + lr = state.get_hyper("learning_rate", grad.dtype.base_dtype) + return training_ops.resource_apply_gradient_descent( + handle.handle, lr, grad, use_locking=self._use_locking) + + def _resource_apply_sparse_duplicate_indices(self, grad, handle, indices, + state): + lr = state.get_hyper("learning_rate", grad.dtype.base_dtype) + return resource_variable_ops.resource_scatter_add(handle.handle, indices, + -grad * lr) + + def _apply_sparse_duplicate_indices(self, grad, var, state): + delta = ops.IndexedSlices( + grad.values * state.get_hyper("learning_rate", var.dtype.base_dtype), + grad.indices, grad.dense_shape) + return var.scatter_sub(delta, use_locking=self._use_locking) diff --git a/tensorflow/contrib/optimizer_v2/momentum.py b/tensorflow/contrib/optimizer_v2/momentum.py index 0636f7e356..0a5aadc2d1 100644 --- a/tensorflow/contrib/optimizer_v2/momentum.py +++ b/tensorflow/contrib/optimizer_v2/momentum.py @@ -18,11 +18,11 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.keras.optimizer_v2 import sgd -from tensorflow.python.util import deprecation +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.training import training_ops -class MomentumOptimizer(sgd.SGD): +class MomentumOptimizer(optimizer_v2.OptimizerV2): """Optimizer that implements the Momentum algorithm. Computes (if `use_nesterov = False`): @@ -39,10 +39,6 @@ class MomentumOptimizer(sgd.SGD): when that part of the variable was used in the forward pass. """ - @deprecation.deprecated_args( - "2018-10-01", - "`use_locking = True` is no longer supported and will be ignored.", - ("use_locking", [False])) def __init__(self, learning_rate, momentum, use_locking=False, name="Momentum", use_nesterov=False): """Construct a new Momentum optimizer. @@ -72,8 +68,57 @@ class MomentumOptimizer(sgd.SGD): optimizer functions. @end_compatibility """ - super(MomentumOptimizer, self).__init__( - learning_rate=learning_rate, - momentum=momentum, - name=name, - nesterov=use_nesterov) + super(MomentumOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + self._set_hyper("momentum", momentum) + self._use_nesterov = use_nesterov + + def _create_vars(self, var_list, state): + for v in var_list: + state.zeros_slot(v, "momentum") + + def _apply_dense(self, grad, var, state): + mom = state.get_slot(var, "momentum") + return training_ops.apply_momentum( + var, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + state.get_hyper("momentum", var.dtype.base_dtype), + use_locking=self._use_locking, + use_nesterov=self._use_nesterov).op + + def _resource_apply_dense(self, grad, var, state): + mom = state.get_slot(var, "momentum") + return training_ops.resource_apply_momentum( + var.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + state.get_hyper("momentum", var.dtype.base_dtype), + use_locking=self._use_locking, + use_nesterov=self._use_nesterov) + + def _apply_sparse(self, grad, var, state): + mom = state.get_slot(var, "momentum") + return training_ops.sparse_apply_momentum( + var, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad.values, + grad.indices, + state.get_hyper("momentum", var.dtype.base_dtype), + use_locking=self._use_locking, + use_nesterov=self._use_nesterov).op + + def _resource_apply_sparse(self, grad, var, indices, state): + mom = state.get_slot(var, "momentum") + return training_ops.resource_sparse_apply_momentum( + var.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + grad, + indices, + state.get_hyper("momentum", var.dtype.base_dtype), + use_locking=self._use_locking, + use_nesterov=self._use_nesterov) diff --git a/tensorflow/contrib/optimizer_v2/optimizer_v2.py b/tensorflow/contrib/optimizer_v2/optimizer_v2.py index 9c98dd93b4..b00262e1f3 100644 --- a/tensorflow/contrib/optimizer_v2/optimizer_v2.py +++ b/tensorflow/contrib/optimizer_v2/optimizer_v2.py @@ -20,11 +20,471 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.keras.optimizer_v2 import optimizer_v2 -from tensorflow.python.util import deprecation +import abc +from tensorflow.python.eager import backprop +from tensorflow.python.eager import context +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import gradients +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import resource_variable_ops +from tensorflow.python.ops import variable_scope +from tensorflow.python.ops import variables +from tensorflow.python.training import distribute as distribute_lib +from tensorflow.python.training import distribution_strategy_context +from tensorflow.python.training import optimizer as optimizer_v1 +from tensorflow.python.training import slot_creator +from tensorflow.python.training.checkpointable import base as checkpointable +from tensorflow.python.util import nest -class OptimizerV2(optimizer_v2.OptimizerV2): + +class _OptimizableVariable(object): + """Interface for abstracting over variables in the optimizers.""" + + @abc.abstractmethod + def target(self): + """Returns the optimization target for this variable.""" + raise NotImplementedError("Calling an abstract method.") + + @abc.abstractmethod + def update_op(self, optimizer, g, *args): + """Returns the update ops for updating the variable.""" + raise NotImplementedError("Calling an abstract method.") + + +class _RefVariableProcessor(_OptimizableVariable): + """Processor for Variable.""" + + def __init__(self, v): + self._v = v + + def target(self): + return self._v._ref() # pylint: disable=protected-access + + def update_op(self, optimizer, g, *args): + if isinstance(g, ops.Tensor): + update_op = optimizer._apply_dense(g, self._v, *args) # pylint: disable=protected-access + if self._v.constraint is not None: + with ops.control_dependencies([update_op]): + return self._v.assign(self._v.constraint(self._v)) + else: + return update_op + else: + assert isinstance(g, ops.IndexedSlices), ("Gradient ", g, " is neither a " + "tensor nor IndexedSlices.") + if self._v.constraint is not None: + raise RuntimeError( + "Cannot use a constraint function on a sparse variable.") + # pylint: disable=protected-access + return optimizer._apply_sparse_duplicate_indices(g, self._v, *args) + + +class _DenseReadResourceVariableProcessor(_OptimizableVariable): + """Processor for dense ResourceVariables.""" + + def __init__(self, v): + self._v = v + + def target(self): + return self._v + + def update_op(self, optimizer, g, *args): + # pylint: disable=protected-access + update_op = optimizer._resource_apply_dense(g, self._v.op.inputs[0], *args) + if self._v.constraint is not None: + with ops.control_dependencies([update_op]): + return self._v.assign(self._v.constraint(self._v)) + else: + return update_op + + +class _DenseResourceVariableProcessor(_OptimizableVariable): + """Processor for dense ResourceVariables.""" + + def __init__(self, v): + self._v = v + + def target(self): + return self._v + + def update_op(self, optimizer, g, *args): + # pylint: disable=protected-access + if isinstance(g, ops.IndexedSlices): + if self._v.constraint is not None: + raise RuntimeError( + "Cannot use a constraint function on a sparse variable.") + return optimizer._resource_apply_sparse_duplicate_indices( + g.values, self._v, g.indices, *args) + update_op = optimizer._resource_apply_dense(g, self._v, *args) + if self._v.constraint is not None: + with ops.control_dependencies([update_op]): + return self._v.assign(self._v.constraint(self._v)) + else: + return update_op + + +class _TensorProcessor(_OptimizableVariable): + """Processor for ordinary Tensors. + + Even though a Tensor can't really be updated, sometimes it is useful to + compute the gradients with respect to a Tensor using the optimizer. Updating + the Tensor is, of course, unsupported. + """ + + def __init__(self, v): + self._v = v + + def target(self): + return self._v + + def update_op(self, optimizer, g, *args): + raise NotImplementedError("Trying to update a Tensor ", self._v) + + +def _get_processor(v): + """The processor of v.""" + if context.executing_eagerly(): + if isinstance(v, ops.Tensor): + return _TensorProcessor(v) + else: + return _DenseResourceVariableProcessor(v) + if v.op.type == "VarHandleOp": + return _DenseResourceVariableProcessor(v) + if isinstance(v, variables.Variable): + return _RefVariableProcessor(v) + if isinstance(v, ops.Tensor): + return _TensorProcessor(v) + raise NotImplementedError("Trying to optimize unsupported type ", v) + + +def _var_key_v2(var): + """Key for representing a primary variable, for looking up slots.""" + # pylint: disable=protected-access + if hasattr(var, "_distributed_container"): + distributed_container = var._distributed_container() + assert distributed_container is not None + if context.executing_eagerly(): + return distributed_container._unique_id + return distributed_container._shared_name + if context.executing_eagerly(): + return var._unique_id + return var.op.name + + +def _resolve(value, name): + if callable(value): + value = value() + return ops.convert_to_tensor(value, name=name) + + +def _is_dynamic(value): + """Returns true if __init__ arg `value` should be re-evaluated each step.""" + if callable(value): + return True + # Don't need to do anything special in graph mode, since dynamic values + # will propagate correctly automatically. + # TODO(josh11b): Add per-device caching across steps using variables for + # truly static values once we add distributed support. + if context.executing_eagerly() and isinstance( + value, resource_variable_ops.ResourceVariable): + return True + return False + + +class _OptimizerV2State(object): + """Holds per-graph and per-step optimizer state. + + Use _init_with_static_hyper() to create the state for a graph, and then + _copy_with_dynamic_hyper() to convert that to state for a particular step. + The difference between the two is that the former only has hyper + parameter values that are static and the latter also has values that + can change every step (according to _is_dynamic()). + """ + + def __init__(self, op_name): + self._op_name = op_name + + def _init_with_static_hyper(self, hyper): + """Initialize a fresh state object from hyper dict.""" + # self._hyper contains a dict from name to a dict with the Tensor values. + # This dict starts with a single item with key "None" with the hyper + # parameter value converted to a Tensor. Other items have dtype keys + # with that Tensor cast to that dtype. + with ops.init_scope(): + self._hyper = { + name: { + None: ops.convert_to_tensor(value, name=name) + } for name, (dynamic, value) in sorted(hyper.items()) if not dynamic + } + self._slots = {} + self._non_slot_dict = {} + # Extra state to help Optimizers implement Checkpointable. Holds information + # about variables which will be restored as soon as they're created. + self._deferred_dependencies = {} # Non-slot variables + self._deferred_slot_restorations = {} # Slot variables + + def _copy_with_dynamic_hyper(self, hyper, distribution, non_slot_devices): + """Create a new state object for a particular step.""" + ret = _OptimizerV2State(self._op_name) + # pylint: disable=protected-access + ret._slots = self._slots + ret._non_slot_dict = self._non_slot_dict + ret._deferred_dependencies = self._deferred_dependencies + ret._deferred_slot_restorations = self._deferred_slot_restorations + ret._hyper = { + name: { + None: _resolve(value, name) + } for name, (dynamic, value) in sorted(hyper.items()) if dynamic + } + ret._hyper.update(self._hyper) + ret._non_slot_devices = non_slot_devices + ret._distribution = distribution + return ret + + def _variables(self): + """Returns a list of all variables held by self.""" + optimizer_variables = list(self._non_slot_dict.values()) + for variable_dict in self._slots.values(): + for slot_for_variable in variable_dict.values(): + optimizer_variables.append(slot_for_variable) + # Sort variables by name so that the return is deterministic. + return sorted(optimizer_variables, key=lambda v: v.name) + + def _slot_dict(self, slot_name): + """Returns a dict for caching slots created under the given name. + + Args: + slot_name: Name for the slot. + + Returns: + A dict that maps primary `Variable` objects to the slot created + for that variable, under the given slot name. + """ + named_slots = self._slots.get(slot_name, None) + if named_slots is None: + named_slots = {} + self._slots[slot_name] = named_slots + return named_slots + + def create_slot(self, var, val, slot_name, optional_op_name=None): + """Find or create a slot for a variable. + + Args: + var: A `Variable` object. + val: A `Tensor`. The initial value of the slot. + slot_name: Name for the slot. + optional_op_name: Name to use when scoping the Variable that needs to be + created for the slot. + + Returns: + A `Variable` object. + """ + named_slots = self._slot_dict(slot_name) + var_key = _var_key_v2(var) + if var_key not in named_slots: + new_slot_variable = slot_creator.create_slot( + var, val, optional_op_name or self._op_name) + self._restore_slot_variable( + slot_name=slot_name, variable=var, slot_variable=new_slot_variable) + named_slots[var_key] = new_slot_variable + return named_slots[var_key] + + def create_slot_with_initializer(self, + var, + initializer, + shape, + dtype, + slot_name, + optional_op_name=None): + """Find or create a slot for a variable, using an Initializer. + + Args: + var: A `Variable` object. + initializer: An `Initializer`. The initial value of the slot. + shape: Shape of the initial value of the slot. + dtype: Type of the value of the slot. + slot_name: Name for the slot. + optional_op_name: Name to use when scoping the Variable that needs to be + created for the slot. + + Returns: + A `Variable` object. + """ + named_slots = self._slot_dict(slot_name) + var_key = _var_key_v2(var) + if var_key not in named_slots: + new_slot_variable = slot_creator.create_slot_with_initializer( + var, initializer, shape, dtype, optional_op_name or self._op_name) + self._restore_slot_variable( + slot_name=slot_name, variable=var, slot_variable=new_slot_variable) + named_slots[var_key] = new_slot_variable + return named_slots[var_key] + + def zeros_slot(self, var, slot_name, optional_op_name=None): + """Find or create a slot initialized with 0.0. + + Args: + var: A `Variable` object. + slot_name: Name for the slot. + optional_op_name: Name to use when scoping the Variable that needs to be + created for the slot. + + Returns: + A `Variable` object. + """ + named_slots = self._slot_dict(slot_name) + var_key = _var_key_v2(var) + if var_key not in named_slots: + new_slot_variable = slot_creator.create_zeros_slot( + var, optional_op_name or self._op_name) + self._restore_slot_variable( + slot_name=slot_name, variable=var, slot_variable=new_slot_variable) + named_slots[var_key] = new_slot_variable + return named_slots[var_key] + + def _create_or_restore_slot_variable(self, + slot_variable_position, + slot_name, + variable, + optional_op_name=None): + """Restore a slot variable's value, possibly creating it. + + Called when a variable which has an associated slot variable is created or + restored. When executing eagerly, we create the slot variable with a + restoring initializer. + + No new variables are created when graph building. Instead, + _restore_slot_variable catches these after normal creation and adds restore + ops to the graph. This method is nonetheless important when graph building + for the case when a slot variable has already been created but `variable` + has just been added to a dependency graph (causing us to realize that the + slot variable needs to be restored). + + Args: + slot_variable_position: A `checkpointable._CheckpointPosition` object + indicating the slot variable `Checkpointable` object to be restored. + slot_name: The name of this `Optimizer`'s slot to restore into. + variable: The variable object this slot is being created for. + optional_op_name: Name to use when scoping the Variable that needs to be + created for the slot. + """ + slot_variable = self.get_slot(var=variable, name=slot_name) + if (slot_variable is None and context.executing_eagerly() and + slot_variable_position.is_simple_variable() + # Defer slot variable creation if there is an active variable creator + # scope. Generally we'd like to eagerly create/restore slot variables + # when possible, but this may mean that scopes intended to catch + # `variable` also catch its eagerly created slot variable + # unintentionally (specifically make_template would add a dependency on + # a slot variable if not for this case). Deferring is mostly harmless + # (aside from double initialization), and makes variable creator scopes + # behave the same way they do when graph building. + and not ops.get_default_graph()._variable_creator_stack): # pylint: disable=protected-access + initializer = checkpointable.CheckpointInitialValue( + checkpoint_position=slot_variable_position) + slot_variable = self.create_slot( + var=variable, + val=initializer, + slot_name=slot_name, + optional_op_name=optional_op_name) + # Optimizers do not have unconditional dependencies on their slot + # variables (nor do any other objects). They are only saved if the + # variables they were created for are also saved. + if slot_variable is not None: + # If we've either made this slot variable, or if we've pulled out an + # existing slot variable, we should restore it. + slot_variable_position.restore(slot_variable) + else: + # We didn't make the slot variable. Defer restoring until it gets created + # normally. We keep a list rather than the one with the highest restore + # UID in case slot variables have their own dependencies, in which case + # those could differ between restores. + variable_key = _var_key_v2(variable) + self._deferred_slot_restorations.setdefault(slot_name, {}).setdefault( + variable_key, []).append(slot_variable_position) + + def get_slot(self, var, name): + """Return a slot named `name` created for `var` by the Optimizer. + + Some `Optimizer` subclasses use additional variables. For example + `Momentum` and `Adagrad` use variables to accumulate updates. This method + gives access to these `Variable` objects if for some reason you need them. + + Use `get_slot_names()` to get the list of slot names created by the + `Optimizer`. + + Args: + var: A variable passed to `minimize()` or `apply_gradients()`. + name: A string. + + Returns: + The `Variable` for the slot if it was created, `None` otherwise. + """ + named_slots = self._slots.get(name, None) + if not named_slots: + return None + return named_slots.get(_var_key_v2(var), None) + + def get_slot_names(self): + """Return a list of the names of slots created by the `Optimizer`. + + See `get_slot()`. + + Returns: + A list of strings. + """ + return sorted(self._slots.keys()) + + def create_non_slot(self, initial_value, name, colocate_with=None): + """Add an extra variable, not associated with a slot.""" + v = self._non_slot_dict.get(name, None) + if v is None: + if colocate_with is None: + colocate_with = self._non_slot_devices + with self._distribution.colocate_vars_with(colocate_with): + # TODO(josh11b): Use get_variable() except for the legacy Adam use case. + v = variable_scope.variable(initial_value, name=name, trainable=False) + self._non_slot_dict[name] = v + deferred_dependencies_list = self._deferred_dependencies.pop(name, ()) + for checkpoint_position in sorted( + deferred_dependencies_list, + key=lambda restore: restore.checkpoint.restore_uid, + reverse=True): + checkpoint_position.restore(v) + return v + + def _restore_slot_variable(self, slot_name, variable, slot_variable): + """Restore a newly created slot variable's value.""" + variable_key = _var_key_v2(variable) + deferred_restorations = self._deferred_slot_restorations.get( + slot_name, {}).pop(variable_key, []) + # Iterate over restores, highest restore UID first to minimize the number + # of assignments. + deferred_restorations.sort( + key=lambda position: position.restore_uid, reverse=True) + for checkpoint_position in deferred_restorations: + checkpoint_position.restore(slot_variable) + + def get_non_slot(self, name): + """Returns the non-slot variable identified by `name`.""" + return self._non_slot_dict.get(name, None) + + def get_hyper(self, name, dtype=None): + """Returns the `name` hyper parameter, optionally cast to `dtype`.""" + dtype_dict = self._hyper[name] + # Do we have the value cast to dtype already cached? This should always + # succeed when dtype is None. + if dtype in dtype_dict: + return dtype_dict[dtype] + # Not cached, cast to dtype and save the result in the cache. + result = math_ops.cast(dtype_dict[None], dtype) + dtype_dict[dtype] = result + return result + + +class OptimizerV2(optimizer_v1.Optimizer): """Updated base class for optimizers. This class defines the API to add Ops to train a model. You never use this @@ -135,10 +595,6 @@ class OptimizerV2(optimizer_v2.OptimizerV2): GATE_OP = 1 GATE_GRAPH = 2 - @deprecation.deprecated_args( - "2018-10-01", - "`use_locking = True` is no longer supported and will be ignored.", - ("use_locking", [False])) def __init__(self, use_locking, name): """Create a new Optimizer. @@ -159,4 +615,758 @@ class OptimizerV2(optimizer_v2.OptimizerV2): RuntimeError: If _create_slots has been overridden instead of _create_vars. """ - super(OptimizerV2, self).__init__(name) + # Note: We intentionally don't call parent __init__. + + # Optimizer._create_slots was replaced by _create_vars in OptimizerV2. + if (self.__class__._create_slots.__code__ is not # pylint: disable=protected-access + OptimizerV2._create_slots.__code__): + raise RuntimeError( + "Override _create_vars instead of _create_slots when " + "descending from OptimizerV2 (class %s)" % self.__class__.__name__) + if not name: + raise ValueError("Must specify the optimizer name") + + self._use_locking = use_locking + self._name = name + # Map from graph_key to state for that graph. We use the graph_key + # since it works in both eager and graph mode, and gives the outer + # graph inside functions. + tower_context = distribution_strategy_context.get_tower_context() + if tower_context is None: + # In a cross-tower context for a DistributionStrategy, which means + # only one Optimizer will be created, not one per tower. + self._per_graph_state = {} + else: + # We use get_tower_context().merge_call() to get a single dict + # shared across all model replicas when running with a + # DistributionStrategy. + self._per_graph_state = tower_context.merge_call(lambda _: {}) + + # Hyper parameters, and whether they should be re-evaluated every step. + self._hyper = {} + + def _set_hyper(self, name, value): + self._hyper[name] = (_is_dynamic(value), value) + + def minimize(self, + loss, + global_step=None, + var_list=None, + gate_gradients=GATE_OP, + aggregation_method=None, + colocate_gradients_with_ops=False, + name=None, + grad_loss=None, + stop_gradients=None, + scale_loss_by_num_towers=None): + """Add operations to minimize `loss` by updating `var_list`. + + This method simply combines calls `compute_gradients()` and + `apply_gradients()`. If you want to process the gradient before applying + them call `compute_gradients()` and `apply_gradients()` explicitly instead + of using this function. + + Args: + loss: A `Tensor` containing the value to minimize. + global_step: Optional `Variable` to increment by one after the variables + have been updated. + var_list: Optional list or tuple of `Variable` objects to update to + minimize `loss`. Defaults to the list of variables collected in the + graph under the key `GraphKeys.TRAINABLE_VARIABLES`. + gate_gradients: How to gate the computation of gradients. Can be + `GATE_NONE`, `GATE_OP`, or `GATE_GRAPH`. + aggregation_method: Specifies the method used to combine gradient terms. + Valid values are defined in the class `AggregationMethod`. + colocate_gradients_with_ops: If True, try colocating gradients with the + corresponding op. + name: Optional name for the returned operation. + grad_loss: Optional. A `Tensor` holding the gradient computed for `loss`. + stop_gradients: Optional. A Tensor or list of tensors not to differentiate + through. + scale_loss_by_num_towers: Optional boolean. If true, scale the loss down + by the number of towers. By default, auto-detects whether this is + needed. + + Returns: + An Operation that updates the variables in `var_list`. If `global_step` + was not `None`, that operation also increments `global_step`. + + Raises: + ValueError: If some of the variables are not `Variable` objects. + + @compatibility(eager) + When eager execution is enabled, `loss` should be a Python function that + takes elements of `var_list` as arguments and computes the value to be + minimized. If `var_list` is None, `loss` should take no arguments. + Minimization (and gradient computation) is done with respect to the + elements of `var_list` if not None, else with respect to any trainable + variables created during the execution of the `loss` function. + `gate_gradients`, `aggregation_method`, `colocate_gradients_with_ops` and + `grad_loss` are ignored when eager execution is enabled. + @end_compatibility + """ + grads_and_vars = self.compute_gradients( + loss, + var_list=var_list, + gate_gradients=gate_gradients, + aggregation_method=aggregation_method, + colocate_gradients_with_ops=colocate_gradients_with_ops, + grad_loss=grad_loss, + stop_gradients=stop_gradients, + scale_loss_by_num_towers=scale_loss_by_num_towers) + + vars_with_grad = [v for g, v in grads_and_vars if g is not None] + if not vars_with_grad: + raise ValueError( + "No gradients provided for any variable, check your graph for ops" + " that do not support gradients, between variables %s and loss %s." % + ([str(v) for _, v in grads_and_vars], loss)) + + return self.apply_gradients( + grads_and_vars, global_step=global_step, name=name) + + def compute_gradients(self, + loss, + var_list=None, + gate_gradients=GATE_OP, + aggregation_method=None, + colocate_gradients_with_ops=False, + grad_loss=None, + stop_gradients=None, + scale_loss_by_num_towers=None): + """Compute gradients of `loss` for the variables in `var_list`. + + This is the first part of `minimize()`. It returns a list + of (gradient, variable) pairs where "gradient" is the gradient + for "variable". Note that "gradient" can be a `Tensor`, an + `IndexedSlices`, or `None` if there is no gradient for the + given variable. + + Args: + loss: A Tensor containing the value to minimize or a callable taking no + arguments which returns the value to minimize. When eager execution is + enabled it must be a callable. + var_list: Optional list or tuple of `tf.Variable` to update to minimize + `loss`. Defaults to the list of variables collected in the graph under + the key `GraphKeys.TRAINABLE_VARIABLES`. + gate_gradients: How to gate the computation of gradients. Can be + `GATE_NONE`, `GATE_OP`, or `GATE_GRAPH`. + aggregation_method: Specifies the method used to combine gradient terms. + Valid values are defined in the class `AggregationMethod`. + colocate_gradients_with_ops: If True, try colocating gradients with the + corresponding op. + grad_loss: Optional. A `Tensor` holding the gradient computed for `loss`. + stop_gradients: Optional. A Tensor or list of tensors not to differentiate + through. + scale_loss_by_num_towers: Optional boolean. If true, scale the loss down + by the number of towers. By default, auto-detects whether this is + needed. + + Returns: + A list of (gradient, variable) pairs. Variable is always present, but + gradient can be `None`. + + Raises: + TypeError: If `var_list` contains anything else than `Variable` objects. + ValueError: If some arguments are invalid. + RuntimeError: If called with eager execution enabled and `loss` is + not callable. + + @compatibility(eager) + When eager execution is enabled, `gate_gradients`, `aggregation_method`, + and `colocate_gradients_with_ops` are ignored. + @end_compatibility + """ + # TODO(josh11b): Test that we handle weight decay in a reasonable way. + if callable(loss): + with backprop.GradientTape() as tape: + if var_list is not None: + tape.watch(var_list) + loss_value = loss() + + # Scale loss for number of towers (callable-loss case). In this case, + # we have to be careful to call distribute_lib.get_loss_reduction() + # *after* loss() is evaluated, so we know what loss reduction it uses. + if scale_loss_by_num_towers is None: + scale_loss_by_num_towers = ( + distribute_lib.get_loss_reduction() == variable_scope + .VariableAggregation.MEAN) + if scale_loss_by_num_towers: + num_towers = distribution_strategy_context.get_distribution_strategy( + ).num_towers + if num_towers > 1: + loss_value *= 1. / num_towers + + if var_list is None: + var_list = tape.watched_variables() + grads = tape.gradient(loss_value, var_list, grad_loss) + return list(zip(grads, var_list)) + if context.executing_eagerly(): + raise RuntimeError("`loss` passed to Optimizer.compute_gradients should " + "be a function when eager execution is enabled.") + + # Scale loss for number of towers (non-callable-loss case). + if scale_loss_by_num_towers is None: + scale_loss_by_num_towers = ( + distribute_lib.get_loss_reduction() == variable_scope + .VariableAggregation.MEAN) + if scale_loss_by_num_towers: + num_towers = distribution_strategy_context.get_distribution_strategy( + ).num_towers + if num_towers > 1: + loss *= 1. / num_towers + + if gate_gradients not in [ + optimizer_v1.Optimizer.GATE_NONE, optimizer_v1.Optimizer.GATE_OP, + optimizer_v1.Optimizer.GATE_GRAPH + ]: + raise ValueError( + "gate_gradients must be one of: Optimizer.GATE_NONE, " + "Optimizer.GATE_OP, Optimizer.GATE_GRAPH. Not %s" % gate_gradients) + self._assert_valid_dtypes([loss]) + if grad_loss is not None: + self._assert_valid_dtypes([grad_loss]) + if var_list is None: + var_list = ( + variables.trainable_variables() + ops.get_collection( + ops.GraphKeys.TRAINABLE_RESOURCE_VARIABLES)) + else: + var_list = nest.flatten(var_list) + # pylint: disable=protected-access + var_list += ops.get_collection(ops.GraphKeys._STREAMING_MODEL_PORTS) + # pylint: enable=protected-access + processors = [_get_processor(v) for v in var_list] + if not var_list: + raise ValueError("No variables to optimize.") + var_refs = [p.target() for p in processors] + grads = gradients.gradients( + loss, + var_refs, + grad_ys=grad_loss, + gate_gradients=(gate_gradients == optimizer_v1.Optimizer.GATE_OP), + aggregation_method=aggregation_method, + colocate_gradients_with_ops=colocate_gradients_with_ops, + stop_gradients=stop_gradients) + if gate_gradients == optimizer_v1.Optimizer.GATE_GRAPH: + grads = control_flow_ops.tuple(grads) + grads_and_vars = list(zip(grads, var_list)) + self._assert_valid_dtypes([ + v for g, v in grads_and_vars + if g is not None and v.dtype != dtypes.resource + ]) + return grads_and_vars + + def apply_gradients(self, grads_and_vars, global_step=None, name=None): + """Apply gradients to variables. + + This is the second part of `minimize()`. It returns an `Operation` that + applies gradients. + + Args: + grads_and_vars: List of (gradient, variable) pairs as returned by + `compute_gradients()`. + global_step: Optional `Variable` to increment by one after the variables + have been updated. + name: Optional name for the returned operation. Default to the name + passed to the `Optimizer` constructor. + + Returns: + An `Operation` that applies the specified gradients. If `global_step` + was not None, that operation also increments `global_step`. + + Raises: + TypeError: If `grads_and_vars` is malformed. + ValueError: If none of the variables have gradients. + """ + # This is a default implementation of apply_gradients() that can be shared + # by most optimizers. It relies on the subclass implementing the following + # methods: _create_vars(), _prepare(), _apply_dense(), and _apply_sparse(). + + # Filter out variables with gradients of `None`. + grads_and_vars = tuple(grads_and_vars) # Make sure repeat iteration works. + if not grads_and_vars: + raise ValueError("No variables provided.") + filtered = tuple((g, v) for (g, v) in grads_and_vars if g is not None) + if not filtered: + raise ValueError("No gradients provided for any variable: %s." % + ([str(v) for _, v in grads_and_vars],)) + return distribution_strategy_context.get_tower_context().merge_call( + self._distributed_apply, filtered, global_step=global_step, name=name) + + def _get_or_create_state(self, var_list=None): + """Either looks up or creates `_OptimizerV2State`. + + If any variables are available, they should be passed via the `var_list` + argument, and these will be used to determine the graph to create/retrieve + state for. Otherwise the returned state is for the current default graph. + + Args: + var_list: A list of variables to extract a graph from. + + Returns: + An `_OptimizerV2State` object. + """ + # Determine the graph_key from the current graph. + eager_execution = context.executing_eagerly() + if eager_execution or var_list is None: + graph = ops.get_default_graph() + else: + graph = ops._get_graph_from_inputs(var_list) # pylint: disable=protected-access + assert graph is not None + graph_key = graph._graph_key # pylint: disable=protected-access + + # Get the per graph state by looking up the graph_key. + if graph_key in self._per_graph_state: + per_graph_state = self._per_graph_state[graph_key] + else: + per_graph_state = _OptimizerV2State(self._name) + per_graph_state._init_with_static_hyper(self._hyper) # pylint: disable=protected-access + self._per_graph_state[graph_key] = per_graph_state + return per_graph_state + + def _distributed_apply(self, distribution, grads_and_vars, global_step, name): + """`apply_gradients` for use with a `DistributionStrategy`.""" + reduced_grads = distribution.batch_reduce( + variable_scope.VariableAggregation.SUM, grads_and_vars) + var_list = [v for _, v in grads_and_vars] + grads_and_vars = zip(reduced_grads, var_list) + + unwrapped_var_list = [x for v in var_list for x in distribution.unwrap(v)] + eager_execution = context.executing_eagerly() + if eager_execution: + # Give a clear error in this case instead of "name not supported + # for Eager Tensors" when we compute non_slot_devices. + for v in unwrapped_var_list: + if isinstance(v, ops.Tensor): + raise NotImplementedError("Trying to update a Tensor ", v) + + with ops.name_scope(name, self._name) as name: + per_graph_state = self._get_or_create_state(var_list=unwrapped_var_list) + # Include the current value of any dynamic hyper parameters in `state`. + non_slot_devices = distribution.non_slot_devices(var_list) + state = per_graph_state._copy_with_dynamic_hyper( # pylint: disable=protected-access + self._hyper, distribution, non_slot_devices) + + # Create any slot and non-slot variables we need in `state`. + with ops.init_scope(): + self._create_vars(var_list, state) + + with ops.name_scope(name): # Re-enter name_scope created above + # Give the child class a chance to do something before we start + # applying gradients. + self._prepare(state) + + def update(v, g): + """Update variable `v` using gradient `g`.""" + assert v is not None + + # Convert the grad to Tensor or IndexedSlices if necessary, and + # look up a processor for each variable's type. + try: + g = ops.convert_to_tensor_or_indexed_slices(g) + except TypeError: + raise TypeError("Gradient must be convertible to a Tensor" + " or IndexedSlices, or None: %s" % g) + if not isinstance(g, (ops.Tensor, ops.IndexedSlices)): + raise TypeError( + "Gradient must be a Tensor, IndexedSlices, or None: %s" % g) + processor = _get_processor(v) + + # We colocate all ops created in _apply_dense or _apply_sparse + # on the same device as the variable. + # TODO(apassos): figure out how to get the variable name here. + scope_name = "" if eager_execution else v.op.name + # device_policy is set because non-mirrored tensors will be read in + # `update_op`. + # TODO(josh11b): Make different state objects for each device to + # avoid needing to set the device_policy. + device_policy = context.context().device_policy( + context.DEVICE_PLACEMENT_SILENT) + with ops.name_scope("update_" + scope_name), device_policy: + return processor.update_op(self, g, state) + + # Use the processors to update the variables. + update_ops = [] + for grad, var in grads_and_vars: + update_ops.extend(distribution.update(var, update, grad, grouped=False)) + + # Give the child class a chance to do something after applying + # gradients + def finish(): + # TODO(josh11b): Make different state objects for each device to + # avoid needing to set the device_policy. + with context.context().device_policy(context.DEVICE_PLACEMENT_SILENT): + return self._finish(state) + + update_ops = control_flow_ops.group(update_ops) + with ops.control_dependencies([update_ops]): + finish_updates = distribution.update_non_slot( + non_slot_devices, finish, grouped=False) + # We said grouped=False, which means finish_updates is always a list. + # It will be [None] when finish() returns None. + if finish_updates == [None]: + finish_updates = [update_ops] + + # Update `global_step` (if any). + if global_step is None: + apply_updates = distribution.group(finish_updates, name=name) + else: + with ops.control_dependencies(finish_updates): + + def update_global_step(global_step, name): + return global_step.assign_add(1, read_value=False, name=name) + + apply_updates = distribution.update(global_step, update_global_step, + name) + + # Add the training op to the TRAIN_OP graph collection in graph mode. + if not eager_execution: + if isinstance(apply_updates, ops.Tensor): + apply_updates = apply_updates.op + train_op = ops.get_collection_ref(ops.GraphKeys.TRAIN_OP) + if apply_updates not in train_op: + train_op.append(apply_updates) + + return apply_updates + + def get_slot(self, var, name): + """Return a slot named `name` created for `var` by the Optimizer. + + Some `Optimizer` subclasses use additional variables. For example + `Momentum` and `Adagrad` use variables to accumulate updates. This method + gives access to these `Variable` objects if for some reason you need them. + + Use `get_slot_names()` to get the list of slot names created by the + `Optimizer`. + + Args: + var: A variable passed to `minimize()` or `apply_gradients()`. + name: A string. + + Returns: + The `Variable` for the slot if it was created, `None` otherwise. + """ + state = self._get_state_for_var(var) + return state.get_slot(var, name) if state is not None else None + + def get_slot_names(self): + """Return a list of the names of slots created by the `Optimizer`. + + See `get_slot()`. + + Returns: + A list of strings. + """ + state = self._get_per_graph_state() + return state.get_slot_names() if state is not None else [] + + def variables(self): + """A list of variables which encode the current state of `Optimizer`. + + Includes slot variables and additional global variables created by the + optimizer in the current default graph. + + Returns: + A list of variables. + """ + state = self._get_per_graph_state() + return state._variables() if state is not None else [] # pylint: disable=protected-access + + # -------------- + # Methods to be implemented by subclasses if they want to use the + # inherited implementation of apply_gradients() or compute_gradients(). + # -------------- + def _create_vars(self, var_list, state): + """Create all slots needed by the variables and any non-slot variables. + + Args: + var_list: A list of `Variable` objects. + state: An object with these methods: `create_slot(var, val, slot_name, + optional_op_name)`, `create_slot_with_initializer(` `var, initializer, + shape, dtype, slot_name, optional_op_name)`, `zeros_slot(var, slot_name, + optional_op_name)`, `create_non_slot_variable(initial_value, name, + colocate_with)`, `get_hyper(name)` + """ + # No slots needed by default + pass + + def _prepare(self, state): + """Code to execute before applying gradients. + + Note that most uses of _prepare() in Optimizer have been subsumed + by explicit support for hyper parameters in OptimizerV2 + + Args: + state: An object with a `get_hyper(name)` method. + + Returns: + Return value will be ignored. + """ + pass + + def _apply_dense(self, grad, var, state): + """Add ops to apply dense gradients to `var`. + + Args: + grad: A `Tensor`. + var: A `Variable` object. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation`. + """ + raise NotImplementedError() + + def _resource_apply_dense(self, grad, handle, state): + """Add ops to apply dense gradients to the variable `handle`. + + Args: + grad: a `Tensor` representing the gradient. + handle: a `Tensor` of dtype `resource` which points to the variable to be + updated. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation` which updates the value of the variable. + """ + raise NotImplementedError() + + def _resource_apply_sparse_duplicate_indices(self, grad, handle, indices, + state): + """Add ops to apply sparse gradients to `handle`, with repeated indices. + + Optimizers which override this method must deal with repeated indices. See + the docstring of `_apply_sparse_duplicate_indices` for details. By default + the correct behavior, to sum non-unique indices and their associated + gradients, is enforced by first pre-processing `grad` and `indices` and + passing them on to `_resource_apply_sparse`. Optimizers which deal correctly + with duplicate indices may instead override this method to avoid the + overhead of summing. + + Args: + grad: a `Tensor` representing the gradient for the affected indices. + handle: a `Tensor` of dtype `resource` which points to the variable to be + updated. + indices: a `Tensor` of integral type representing the indices for which + the gradient is nonzero. Indices may be repeated. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation` which updates the value of the variable. + """ + # pylint: disable=protected-access + summed_grad, unique_indices = optimizer_v1._deduplicate_indexed_slices( + values=grad, indices=indices) + # pylint: enable=protected-access + return self._resource_apply_sparse(summed_grad, handle, unique_indices, + state) + + def _resource_apply_sparse(self, grad, handle, indices, state): + """Add ops to apply sparse gradients to the variable `handle`. + + Similar to `_apply_sparse`, the `indices` argument to this method has been + de-duplicated. Optimizers which deal correctly with non-unique indices may + instead override `_resource_apply_sparse_duplicate_indices` to avoid this + overhead. + + Args: + grad: a `Tensor` representing the gradient for the affected indices. + handle: a `Tensor` of dtype `resource` which points to the variable to be + updated. + indices: a `Tensor` of integral type representing the indices for which + the gradient is nonzero. Indices are unique. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation` which updates the value of the variable. + """ + raise NotImplementedError() + + def _apply_sparse_duplicate_indices(self, grad, var, state): + """Add ops to apply sparse gradients to `var`, with repeated sparse indices. + + Optimizers which override this method must deal with IndexedSlices objects + such as the following: + + IndexedSlicesValue(values=[1, 1], indices=[0, 0], dense_shape=[1]) + + The correct interpretation is: + + IndexedSlicesValue(values=[2], indices=[0], dense_shape=[1]) + + Many optimizers deal incorrectly with repeated indices when updating based + on sparse gradients (e.g. summing squares rather than squaring the sum, or + applying momentum terms multiple times). Adding first is always the correct + behavior, so this is enforced here by reconstructing the IndexedSlices to + have only unique indices, then calling _apply_sparse. + + Optimizers which deal correctly with repeated indices may instead override + this method to avoid the overhead of summing indices. + + Args: + grad: `IndexedSlices`. + var: A `Variable` object. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation`. + """ + # pylint: disable=protected-access + summed_values, unique_indices = optimizer_v1._deduplicate_indexed_slices( + values=grad.values, indices=grad.indices) + # pylint: enable=protected-access + gradient_no_duplicate_indices = ops.IndexedSlices( + indices=unique_indices, + values=summed_values, + dense_shape=grad.dense_shape) + return self._apply_sparse(gradient_no_duplicate_indices, var, state) + + def _apply_sparse(self, grad, var, state): + """Add ops to apply sparse gradients to `var`. + + The IndexedSlices object passed to `grad` in this function is by default + pre-processed in `_apply_sparse_duplicate_indices` to remove duplicate + indices (see its docstring for details). Optimizers which can tolerate or + have correct special cases for duplicate sparse indices may override + `_apply_sparse_duplicate_indices` instead of this function, avoiding that + overhead. + + Args: + grad: `IndexedSlices`, with no repeated indices. + var: A `Variable` object. + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + An `Operation`. + """ + raise NotImplementedError() + + def _finish(self, state): + """Do what is needed to finish the update. + + This is called inside a scope colocated with any non-slot variables. + + Args: + state: An object with `get_slot(var, name)`, `get_non_slot(self, name)`, + and `get_hyper(name)` methods. + + Returns: + The operation to apply updates, or None if no updates. + """ + return None + + # -------------- + # Utility methods for subclasses. + # -------------- + def _get_per_graph_state(self): + # pylint: disable=protected-access + return self._per_graph_state.get(ops.get_default_graph()._graph_key, None) + + def _get_state_for_var(self, var): + # pylint: disable=protected-access + return self._per_graph_state.get(var._graph_key, None) + + # -------------- + # Overridden methods from Checkpointable. + # -------------- + + def _track_checkpointable(self, *args, **kwargs): + """Optimizers may not track dependencies. Raises an error.""" + raise NotImplementedError( + "Optimizers may not have dependencies. File a feature request if this " + "limitation bothers you.") + + @property + def _checkpoint_dependencies(self): + """From Checkpointable. Gather graph-specific non-slot variables to save.""" + current_graph_non_slot_variables = [] + state = self._get_per_graph_state() + if state is not None: + for name, variable_object in sorted( + state._non_slot_dict.items(), # pylint: disable=protected-access + # Avoid comparing variables + key=lambda item: item[0]): + current_graph_non_slot_variables.append( + checkpointable.CheckpointableReference( + name=name, ref=variable_object)) + # Note: ignores super(); Optimizers may not have any dependencies outside of + # state objects. + return current_graph_non_slot_variables + + def _lookup_dependency(self, name): + """From Checkpointable. Find a non-slot variable in the current graph.""" + state = self._get_per_graph_state() + if state is None: + return None + else: + return state.get_non_slot(name) + + @property + def _deferred_dependencies(self): + """Lets Checkpointable know where non-slot variables are created. + + If necessary, creates a new state object for the current default graph. + Checkpointable will then add entries to that state's deferred dependency + dictionary. The state object will check that dictionary when creating + non-slot variables, restoring their value if an entry is found. + + Returns: + A dictionary which holds deferred dependencies for the current default + graph. + """ + state = self._get_or_create_state() + return state._deferred_dependencies # pylint: disable=protected-access + + def _create_or_restore_slot_variable(self, slot_variable_position, slot_name, + variable): + """Checkpointable: Restore a slot variable's value, possibly creating it. + + Called when a variable which has an associated slot variable is created or + restored. + + Args: + slot_variable_position: A `checkpointable._CheckpointPosition` object + indicating the slot variable `Checkpointable` object to be restored. + slot_name: The name of this `Optimizer`'s slot to restore into. + variable: The variable object this slot is being created for. + """ + state = self._get_or_create_state(var_list=[variable]) + state._create_or_restore_slot_variable( # pylint: disable=protected-access + slot_variable_position=slot_variable_position, + slot_name=slot_name, + variable=variable, + optional_op_name=self._name) + + # -------------- + # Unsupported parent methods + # -------------- + def _slot_dict(self, slot_name): + raise NotImplementedError("_slot_dict() method unsupported in OptimizerV2") + + def _get_or_make_slot(self, var, val, slot_name, op_name): + raise NotImplementedError( + "_get_or_make_slot() method unsupported in OptimizerV2") + + def _get_or_make_slot_with_initializer(self, var, initializer, shape, dtype, + slot_name, op_name): + raise NotImplementedError( + "_get_or_make_slot_with_initializer() method unsupported in " + "OptimizerV2") + + def _create_non_slot_variable(self, initial_value, name, colocate_with): + raise NotImplementedError( + "_create_non_slot_variable() method unsupported in OptimizerV2") + + def _get_non_slot_variable(self, name, graph=None): + raise NotImplementedError( + "_get_non_slot_variable() method unsupported in OptimizerV2") + + def _non_slot_variables(self): + raise NotImplementedError( + "_non_slot_variables() method unsupported in OptimizerV2") diff --git a/tensorflow/contrib/optimizer_v2/rmsprop.py b/tensorflow/contrib/optimizer_v2/rmsprop.py index 090e257ddc..12175cedd3 100644 --- a/tensorflow/contrib/optimizer_v2/rmsprop.py +++ b/tensorflow/contrib/optimizer_v2/rmsprop.py @@ -21,7 +21,7 @@ A detailed description of rmsprop. - maintain a moving (discounted) average of the square of gradients - divide gradient by the root of this average -mean_square = decay * mean_square{t-1} + (1-decay) * gradient ** 2 +mean_square = rho * mean_square{t-1} + (1-rho) * gradient ** 2 mom = momentum * mom{t-1} + learning_rate * g_t / sqrt(mean_square) delta = - mom @@ -30,8 +30,8 @@ This implementation of RMSProp uses plain momentum, not Nesterov momentum. The centered version additionally maintains a moving (discounted) average of the gradients, and uses that average to estimate the variance: -mean_grad = decay * mean_square{t-1} + (1-decay) * gradient -mean_square = decay * mean_square{t-1} + (1-decay) * gradient ** 2 +mean_grad = rho * mean_square{t-1} + (1-rho) * gradient +mean_square = rho * mean_square{t-1} + (1-rho) * gradient ** 2 mom = momentum * mom{t-1} + learning_rate * g_t / sqrt(mean_square - mean_grad**2) delta = - mom @@ -41,21 +41,20 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.keras.optimizer_v2 import rmsprop -from tensorflow.python.util import deprecation +from tensorflow.contrib.optimizer_v2 import optimizer_v2 +from tensorflow.python.ops import array_ops +from tensorflow.python.training import training_ops -class RMSPropOptimizer(rmsprop.RMSProp): + +class RMSPropOptimizer(optimizer_v2.OptimizerV2): """Optimizer that implements the RMSProp algorithm. See the - [paper](http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf). + [paper] + (http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf). """ - @deprecation.deprecated_args( - "2018-10-01", - "`use_locking = True` is no longer supported and will be ignored.", - ("use_locking", [False])) def __init__(self, learning_rate, decay=0.9, @@ -98,10 +97,138 @@ class RMSPropOptimizer(rmsprop.RMSProp): name: Optional name prefix for the operations created when applying gradients. Defaults to "RMSProp". """ - super(RMSPropOptimizer, self).__init__( - learning_rate=learning_rate, - rho=decay, - momentum=momentum, - epsilon=epsilon, - centered=centered, - name=name) + super(RMSPropOptimizer, self).__init__(use_locking, name) + self._set_hyper("learning_rate", learning_rate) + self._set_hyper("decay", decay) + self._set_hyper("momentum", momentum) + self._set_hyper("epsilon", epsilon) + + self._centered = centered + + def _create_vars(self, var_list, state): + for v in var_list: + init_rms = state.get_hyper("epsilon", + v.dtype.base_dtype) * array_ops.ones_like(v) + state.create_slot_with_initializer(v, init_rms, v.get_shape(), + v.dtype.base_dtype, "rms") + if self._centered: + state.zeros_slot(v, "mg") + state.zeros_slot(v, "momentum") + + def _apply_dense(self, grad, var, state): + rms = state.get_slot(var, "rms") + mom = state.get_slot(var, "momentum") + if self._centered: + mg = state.get_slot(var, "mg") + return training_ops.apply_centered_rms_prop( + var, + mg, + rms, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + # epsilon is now the rms initial value and is not added to the + # denominator anymore, hence calling the kernel op with epsilon=0. + 0, + grad, + use_locking=self._use_locking).op + else: + return training_ops.apply_rms_prop( + var, + rms, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + 0, + grad, + use_locking=self._use_locking).op + + def _resource_apply_dense(self, grad, var, state): + rms = state.get_slot(var, "rms") + mom = state.get_slot(var, "momentum") + if self._centered: + mg = state.get_slot(var, "mg") + return training_ops.resource_apply_centered_rms_prop( + var.handle, + mg.handle, + rms.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + 0, + grad, + use_locking=self._use_locking) + else: + return training_ops.resource_apply_rms_prop( + var.handle, + rms.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + 0, + grad, + use_locking=self._use_locking) + + def _apply_sparse(self, grad, var, state): + rms = state.get_slot(var, "rms") + mom = state.get_slot(var, "momentum") + if self._centered: + mg = state.get_slot(var, "mg") + return training_ops.sparse_apply_centered_rms_prop( + var, + mg, + rms, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + 0, + grad.values, + grad.indices, + use_locking=self._use_locking) + else: + return training_ops.sparse_apply_rms_prop( + var, + rms, + mom, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + 0, + grad.values, + grad.indices, + use_locking=self._use_locking) + + def _resource_apply_sparse(self, grad, var, indices, state): + rms = state.get_slot(var, "rms") + mom = state.get_slot(var, "momentum") + if self._centered: + mg = self.get_slot(var, "mg") + return training_ops.resource_sparse_apply_centered_rms_prop( + var.handle, + mg.handle, + rms.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + 0, + grad, + indices, + use_locking=self._use_locking) + else: + return training_ops.resource_sparse_apply_rms_prop( + var.handle, + rms.handle, + mom.handle, + state.get_hyper("learning_rate", var.dtype.base_dtype), + state.get_hyper("decay", var.dtype.base_dtype), + state.get_hyper("momentum", var.dtype.base_dtype), + 0, + grad, + indices, + use_locking=self._use_locking) -- GitLab From e11232bbe90ea8b898600069216f8e94d56c2409 Mon Sep 17 00:00:00 2001 From: Guangda Lai Date: Wed, 17 Oct 2018 16:30:47 -0700 Subject: [PATCH 0236/1825] Cleanup: remove code that are specific to TRT 3.0. PiperOrigin-RevId: 217610670 --- .../contrib/tensorrt/convert/convert_graph.cc | 2 - .../contrib/tensorrt/convert/convert_nodes.cc | 171 ------------------ .../contrib/tensorrt/kernels/trt_engine_op.cc | 8 - .../tensorrt/resources/trt_allocator.cc | 2 - .../tensorrt/resources/trt_allocator.h | 10 - 5 files changed, 193 deletions(-) diff --git a/tensorflow/contrib/tensorrt/convert/convert_graph.cc b/tensorflow/contrib/tensorrt/convert/convert_graph.cc index 4d41761fdb..070dc294a7 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_graph.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_graph.cc @@ -117,7 +117,6 @@ bool IsTensorRTCandidate(const tensorflow::Node* node) { "Neg", "Transpose", "Reshape", -#if NV_TENSORRT_MAJOR > 3 "MatMul", "BatchMatMul", "Softmax", @@ -128,7 +127,6 @@ bool IsTensorRTCandidate(const tensorflow::Node* node) { "Prod", "Max", "Min", -#endif // TODO(ben,jie): ... }; // LINT.ThenChange(//tensorflow/contrib/tensorrt/convert/convert_nodes.cc) diff --git a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc index f57793f46d..eb2dffe185 100644 --- a/tensorflow/contrib/tensorrt/convert/convert_nodes.cc +++ b/tensorflow/contrib/tensorrt/convert/convert_nodes.cc @@ -98,11 +98,9 @@ inline tensorflow::Status ConvertDType(tensorflow::DataType tf_dtype, case tensorflow::DataType::DT_HALF: *trt_dtype = nvinfer1::DataType::kHALF; break; -#if NV_TENSORRT_MAJOR > 3 case tensorflow::DataType::DT_INT32: *trt_dtype = nvinfer1::DataType::kINT32; break; -#endif default: return tensorflow::errors::InvalidArgument( "Unsupported data type ", tensorflow::DataTypeString(tf_dtype)); @@ -707,15 +705,10 @@ Status Converter::PrepareTensorForShape(const TRT_TensorOrWeights& input, *tensor = layer->getOutput(0); } } else { -#if NV_TENSORRT_MAJOR > 3 nvinfer1::IConstantLayer* layer = this->network()->addConstant(dims, input.weights()); TFTRT_RETURN_ERROR_IF_NULLPTR(layer, "TF-TRT Internal Reshape"); *tensor = layer->getOutput(0); -#else - return tensorflow::errors::Unimplemented( - "Can't reshape constant. Please upgrade to TRT 4 or above."); -#endif } return tensorflow::Status::OK(); } @@ -1776,12 +1769,8 @@ tensorflow::Status ConvertBinary(Converter& ctx, } else if (inputs.at(0).is_weights() && inputs.at(1).is_tensor()) { status = BinaryTensorOpWeight(ctx, node_def, inputs.at(1).tensor(), inputs.at(0).weights(), true, outputs); -#if NV_TENSORRT_MAJOR == 3 - } else { -#else } if ((inputs.at(0).is_tensor() && inputs.at(1).is_tensor()) || !status.ok()) { -#endif status = BinaryTensorOpTensor(ctx, node_def, inputs.at(0), inputs.at(1), outputs); } @@ -1806,13 +1795,6 @@ tensorflow::Status ConvertUnary(Converter& ctx, "Unary ops require single tensor input, at ", node_def.name()); } -#if NV_TENSORRT_MAJOR == 3 - if (inputs.at(0).is_weights()) { - return tensorflow::errors::Unimplemented( - "Constant folding for unary op is not supported", node_def.name()); - } -#endif - // TODO(jie): check type const nvinfer1::ITensor* tensor = nullptr; TF_RETURN_IF_ERROR( @@ -1841,103 +1823,6 @@ tensorflow::Status ConvertUnary(Converter& ctx, return tensorflow::Status::OK(); } -#if NV_TENSORRT_MAJOR == 3 -tensorflow::Status ConvertReducePool( - Converter& ctx, const tensorflow::NodeDef& node_def, - const std::vector& inputs, - std::vector* outputs) { - if (inputs.size() != 2 || !inputs.at(0).is_tensor() || - !inputs.at(1).is_weights()) { - return tensorflow::errors::InvalidArgument( - "Input expects tensor and weights, at", node_def.name()); - } - - // Implement tensor binaryOp weight [channel wise] for now; - const nvinfer1::ITensor* tensor = inputs.at(0).tensor(); - const auto dims = tensor->getDimensions(); - // Restore implicit batch dimension - const int nb_dims = dims.nbDims + 1; - - TRT_ShapedWeights index_list = inputs.at(1).weights(); - TFAttrs attrs(node_def); - auto index_type = attrs.get("Tidx"); - - // Only expect to handle INT32 as attributes for now - if (index_type != tensorflow::DataType::DT_INT32) { - return tensorflow::errors::Unimplemented("Tidx supports only DT_INT32"); - } - const auto index_list_data = - static_cast(const_cast(index_list.GetValues())); - - if (nb_dims != 4) { - return tensorflow::errors::InvalidArgument( - "TRT only support reduce on 4 dimensional tensors, at", - node_def.name()); - } - if (index_list.count() > 2) { - return tensorflow::errors::InvalidArgument( - "TRT cannot support reduce on more than 2 dimensions, at", - node_def.name()); - } - - std::set idx_set; - // We cannot operate on Channel. permutation flag used to transpose tensor - int permuted_index = -1; - for (int i = 0; i < index_list.count(); i++) { - if (index_list_data[i] == 0) { - return tensorflow::errors::InvalidArgument("TRT cannot reduce at 0, at", - node_def.name()); - } - if (index_list_data[i] == 1) permuted_index = 1; - idx_set.emplace(index_list_data[i]); - } - - std::vector permutation_order(nb_dims); - nvinfer1::DimsHW pool_kernel; - if (permuted_index == 1) { - for (int i = 2; i < nb_dims; i++) { - if (idx_set.count(i) == 0) { - permuted_index = i; - break; - } - } - for (int i = 0; i < nb_dims; i++) permutation_order[i] = i; - - permutation_order[permuted_index] = 1; - permutation_order[1] = permuted_index; - - // Apply permutation before extracting dimension for pool_kernel - TF_RETURN_IF_ERROR(ctx.TransposeTensor( - const_cast(tensor), permutation_order, &tensor)); - } - - // Apply permutation before extracting dimension for pool_kernel - pool_kernel.d[0] = (idx_set.count(2) || permuted_index == 2) ? dims.d[1] : 1; - pool_kernel.d[1] = (idx_set.count(3) || permuted_index == 3) ? dims.d[2] : 1; - - const nvinfer1::ITensor* output_tensor = nullptr; - - if (node_def.op() == "Mean") { - nvinfer1::IPoolingLayer* layer = - ctx.network()->addPooling(*const_cast(tensor), - nvinfer1::PoolingType::kAVERAGE, pool_kernel); - TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); - output_tensor = layer->getOutput(0); - } else { - return tensorflow::errors::Unimplemented("Op not supported ", node_def.op(), - " , at ", node_def.name()); - } - if (permuted_index != -1) { - // Apply permutation before extracting dimension for pool_kernel - TF_RETURN_IF_ERROR( - ctx.TransposeTensor(const_cast(output_tensor), - permutation_order, &output_tensor)); - } - outputs->push_back( - TRT_TensorOrWeights(const_cast(output_tensor))); - return tensorflow::Status::OK(); -} -#elif NV_TENSORRT_MAJOR > 3 tensorflow::Status ConvertReduce(Converter& ctx, const tensorflow::NodeDef& node_def, const std::vector& inputs, @@ -2003,7 +1888,6 @@ tensorflow::Status ConvertReduce(Converter& ctx, outputs->push_back(TRT_TensorOrWeights(layer->getOutput(0))); return tensorflow::Status::OK(); } -#endif tensorflow::Status ConvertPad(Converter& ctx, const tensorflow::NodeDef& node_def, @@ -2156,18 +2040,6 @@ tensorflow::Status ConvertConcat(Converter& ctx, index = dim.nbDims + index + 1; } -#if NV_TENSORRT_MAJOR == 3 - // incase we need permutation; - std::vector permutation_order(dim.nbDims + 1); - - for (int i = 0; i < dim.nbDims + 1; i++) permutation_order[i] = i; - - if (index != 1) { - permutation_order[1] = index; - permutation_order[index] = 1; - } -#endif - std::vector inputs_vec; // Shap chack (all input tensor should have same shape) // starting from 0 since we are probably also doing transpose here; @@ -2188,14 +2060,6 @@ tensorflow::Status ConvertConcat(Converter& ctx, } } -#if NV_TENSORRT_MAJOR == 3 - // TRT3 does concatenation only on channel! - if (index != 1) { - TF_RETURN_IF_ERROR( - ctx.TransposeTensor(const_cast(tensor_i), - permutation_order, &tensor_i)); - } -#endif inputs_vec.push_back(tensor_i); } @@ -2204,17 +2068,8 @@ tensorflow::Status ConvertConcat(Converter& ctx, const_cast(inputs_vec.data()), inputs_vec.size()); TFTRT_RETURN_ERROR_IF_NULLPTR(layer, node_def.name()); -#if NV_TENSORRT_MAJOR > 3 layer->setAxis(index - 1); -#endif nvinfer1::ITensor* output_tensor = layer->getOutput(0); - -#if NV_TENSORRT_MAJOR == 3 - if (index != 1) { - TF_RETURN_IF_ERROR( - ctx.TransposeTensor(output_tensor, permutation_order, &output_tensor)); - } -#endif outputs->push_back(TRT_TensorOrWeights(output_tensor)); return tensorflow::Status::OK(); } @@ -2339,7 +2194,6 @@ tensorflow::Status ConvertFusedBatchNorm( return tensorflow::Status::OK(); } -#if NV_TENSORRT_MAJOR > 3 tensorflow::Status ConvertMatMulHelper( Converter& ctx, TRT_TensorOrWeights tensor_input, TRT_ShapedWeights weights_raw, bool transpose_weight, string node_name, @@ -2492,9 +2346,7 @@ tensorflow::Status ConvertBatchMatMul( outputs->push_back(TRT_TensorOrWeights(output_tensor)); return tensorflow::Status::OK(); } -#endif -#if NV_TENSORRT_MAJOR > 3 tensorflow::Status ConvertSoftmax( Converter& ctx, const tensorflow::NodeDef& node_def, const std::vector& inputs, @@ -2517,9 +2369,7 @@ tensorflow::Status ConvertSoftmax( outputs->push_back(TRT_TensorOrWeights(output_tensor)); return tensorflow::Status::OK(); } -#endif -#if NV_TENSORRT_MAJOR > 3 tensorflow::Status ConvertTopK(Converter& ctx, const tensorflow::NodeDef& node_def, const std::vector& inputs, @@ -2556,7 +2406,6 @@ tensorflow::Status ConvertTopK(Converter& ctx, outputs->push_back(TRT_TensorOrWeights(output_indices_tensor)); return tensorflow::Status::OK(); } -#endif void Converter::RegisterOpConverters() { // vgg_16 slim implementation @@ -2595,10 +2444,6 @@ void Converter::RegisterOpConverters() { op_registry_["Transpose"] = ConvertTranspose; op_registry_["Reshape"] = ConvertReshape; -#if NV_TENSORRT_MAJOR == 3 - op_registry_["Mean"] = ConvertReducePool; -#endif -#if NV_TENSORRT_MAJOR > 3 op_registry_["Sum"] = ConvertReduce; op_registry_["Prod"] = ConvertReduce; op_registry_["Max"] = ConvertReduce; @@ -2610,7 +2455,6 @@ void Converter::RegisterOpConverters() { op_registry_["MatMul"] = ConvertMatMul; op_registry_["BatchMatMul"] = ConvertBatchMatMul; op_registry_["TopKV2"] = ConvertTopK; -#endif plugin_converter_ = ConvertPlugin; } @@ -2631,9 +2475,7 @@ tensorflow::Status ConvertGraphDefToEngine( nvinfer1::createInferBuilder(*logger)); builder->setMaxBatchSize(max_batch_size); builder->setMaxWorkspaceSize(max_workspace_size_bytes); -#if NV_TENSORRT_MAJOR > 3 builder->setGpuAllocator(allocator); -#endif if (precision_mode == FP16MODE) { builder->setHalf2Mode(true); } else if (precision_mode == INT8MODE) { @@ -2678,11 +2520,7 @@ tensorflow::Status ConvertGraphDefToEngine( return Status(status.code(), error_message); } -#if NV_TENSORRT_MAJOR == 3 - nvinfer1::DimsCHW input_dim; -#elif NV_TENSORRT_MAJOR > 3 nvinfer1::Dims input_dim; -#endif for (int i = 1; i < shape.dims(); i++) { input_dim.d[i - 1] = shape.dim_size(i); } @@ -2898,21 +2736,12 @@ bool InputEdgeValidator::operator()(const tensorflow::Edge* in_edge) const { if (in_edge->src()->type_string() != "Const" && -#if NV_TENSORRT_MAJOR == 3 - // TRT 3.x only support 4 dimensional input tensor. - shape.dims() != 4) { -#else // Single dimensional input tensor is not supported since the first // dimension is treated as batch dimension. shape.dims() < 2) { -#endif VLOG(1) << "--> Need to remove input node " << in_edge->dst()->name() << " which has an input at port " << in_edge->dst_input() << " with" -#if NV_TENSORRT_MAJOR == 3 - << " #dim!=4" -#else << " #dim<2" -#endif << " and is not a const: " << shape; return false; } diff --git a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc index 88cf8d5980..019446813a 100644 --- a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc +++ b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc @@ -333,11 +333,9 @@ bool TRTEngineOp::ExecuteTrtEngine( case nvinfer1::DataType::kINT8: LOG(ERROR) << "INT8 inputs are not supported yet!"; return kRetry; -#if NV_TENSORRT_MAJOR > 3 case nvinfer1::DataType::kINT32: buffers[binding_index] = (void*)(input_tensor.flat().data()); break; -#endif default: LOG(ERROR) << "Unknown TRT data type: " << int(dtype); return kRetry; @@ -387,12 +385,10 @@ bool TRTEngineOp::ExecuteTrtEngine( case nvinfer1::DataType::kINT8: LOG(WARNING) << "int8 is not supported yet!"; return kRetry; -#if NV_TENSORRT_MAJOR > 3 case nvinfer1::DataType::kINT32: buffers[binding_index] = reinterpret_cast(output_tensor->flat().data()); break; -#endif default: LOG(WARNING) << "Unknown TRT data type: " << static_cast(dtype); return kRetry; @@ -457,13 +453,11 @@ TRTEngineOp::EngineCtxPair& TRTEngineOp::GetEngine(int batch_size, return null_pair; } TrtUniquePtrType infer(nvinfer1::createInferRuntime(logger)); -#if NV_TENSORRT_MAJOR > 3 auto allocator = GetAllocator(ctx); if (allocator == nullptr) { return null_pair; } infer->setGpuAllocator(allocator); -#endif TrtUniquePtrType static_engine( infer->deserializeCudaEngine(serialized_segment_.c_str(), serialized_segment_.size(), @@ -487,12 +481,10 @@ TRTEngineOp::EngineCtxPair& TRTEngineOp::GetEngine(int batch_size, if (engine_it == engine_map_.end() && engine_map_.size() < (size_t)max_cached_engines_) { nvinfer1::IGpuAllocator* allocator = nullptr; -#if NV_TENSORRT_MAJOR > 3 allocator = GetAllocator(ctx); if (allocator == nullptr) { return null_pair; } -#endif std::vector shapes; for (int i = 0; i < ctx->num_inputs(); ++i) { shapes.emplace_back(ctx->input(i).shape()); diff --git a/tensorflow/contrib/tensorrt/resources/trt_allocator.cc b/tensorflow/contrib/tensorrt/resources/trt_allocator.cc index a9425864dd..7a2e93414a 100644 --- a/tensorflow/contrib/tensorrt/resources/trt_allocator.cc +++ b/tensorflow/contrib/tensorrt/resources/trt_allocator.cc @@ -54,7 +54,6 @@ void* Align(uint64_t alignment, uint64_t size, void*& ptr, uint64_t& space) { #if GOOGLE_CUDA #if GOOGLE_TENSORRT -#if NV_TENSORRT_MAJOR > 2 namespace tensorflow { namespace tensorrt { @@ -116,6 +115,5 @@ void TRTDeviceAllocator::free(void* memory) { } // namespace tensorrt } // namespace tensorflow -#endif #endif // GOOGLE_TENSORRT #endif // GOOGLE_CUDA diff --git a/tensorflow/contrib/tensorrt/resources/trt_allocator.h b/tensorflow/contrib/tensorrt/resources/trt_allocator.h index dc9862b16c..f857a9de05 100644 --- a/tensorflow/contrib/tensorrt/resources/trt_allocator.h +++ b/tensorflow/contrib/tensorrt/resources/trt_allocator.h @@ -35,16 +35,6 @@ void* Align(uint64_t alignment, uint64_t size, void*& ptr, uint64_t& space); #if GOOGLE_CUDA #if GOOGLE_TENSORRT -#if NV_TENSORRT_MAJOR == 3 -// Define interface here temporarily until TRT 4.0 is released -namespace nvinfer1 { -class IGpuAllocator { - public: - virtual void* allocate(uint64_t size, uint64_t alignment, uint32_t flags) = 0; - virtual void free(void* memory) = 0; -}; -} // namespace nvinfer1 -#endif namespace tensorflow { namespace tensorrt { -- GitLab From 932e56125261d407f83dbc802a6a1b98ba61bbee Mon Sep 17 00:00:00 2001 From: Dan Moldovan Date: Wed, 17 Oct 2018 16:33:46 -0700 Subject: [PATCH 0237/1825] Consolidate the handling of special builtin functions that are not recognized as such by the Python reflection APIs. Enable verbosity in tests. Reduce the size of call_trees_test to default so that it runs with the main tests. PiperOrigin-RevId: 217611165 --- tensorflow/python/autograph/converters/BUILD | 1 - .../python/autograph/converters/call_trees.py | 17 +++++--------- .../autograph/core/converter_testing.py | 4 +++- .../python/autograph/pyct/inspect_utils.py | 22 ++++++++++++++----- .../pyct/static_analysis/live_values.py | 16 ++++---------- 5 files changed, 28 insertions(+), 32 deletions(-) diff --git a/tensorflow/python/autograph/converters/BUILD b/tensorflow/python/autograph/converters/BUILD index f06dc78f0e..0aa5ddd007 100644 --- a/tensorflow/python/autograph/converters/BUILD +++ b/tensorflow/python/autograph/converters/BUILD @@ -84,7 +84,6 @@ py_test( py_test( name = "call_trees_test", - size = "large", srcs = ["call_trees_test.py"], srcs_version = "PY2AND3", tags = ["no_windows"], diff --git a/tensorflow/python/autograph/converters/call_trees.py b/tensorflow/python/autograph/converters/call_trees.py index ca6945266e..3f42180e38 100644 --- a/tensorflow/python/autograph/converters/call_trees.py +++ b/tensorflow/python/autograph/converters/call_trees.py @@ -308,28 +308,21 @@ class CallTreeTransformer(converter.Base): target_fqn = anno.getanno(node.func, 'fqn') else: target_fqn = None + if self._function_is_compilable(target_entity): node = self._rename_compilable_function(node) elif target_fqn and target_fqn in KNOWN_NUMPY_FUNCTIONS: # TODO(mdan): Should we replace these with equivalent TF ops instead? node = self._wrap_to_py_func_single_return( node, KNOWN_NUMPY_FUNCTIONS[target_fqn].dtype) + elif inspect_utils.isbuiltin(target_entity): + # Note: Any builtin that passed the builtins converter is assumed to be + # safe for graph mode. + return node else: raise NotImplementedError( 'py_func with return values (unknown function)') else: - if anno.hasanno(node.func, anno.Basic.QN): - # Special-case a few builtins that otherwise go undetected. This - # normally doesn't pose a problem, but the dict built-in doesn't - # work with inspect.getargspec which is required for dynamic functions. - # Note: expecting this is resilient to aliasing (e.g. - # dict = an_evil_dict), because in those cases the regular mechanisms - # process a simple user function. - qn = anno.getanno(node.func, anno.Basic.QN) - # Add items to this list as needed. - if str(qn) in ('dict',): - return node - if ast_util.matches(node, 'super(_)'): # super() calls are preserved. The class conversion mechanism will # ensure that they return the correct value. diff --git a/tensorflow/python/autograph/core/converter_testing.py b/tensorflow/python/autograph/core/converter_testing.py index c701053124..804fc6fe7e 100644 --- a/tensorflow/python/autograph/core/converter_testing.py +++ b/tensorflow/python/autograph/core/converter_testing.py @@ -162,7 +162,9 @@ class TestCase(test.TestCase): namer = FakeNamer() program_ctx = converter.ProgramContext( options=converter.ConversionOptions( - recursive=recursive, strip_decorators=strip_decorators), + recursive=recursive, + strip_decorators=strip_decorators, + verbose=True), partial_types=None, autograph_module=None, uncompiled_modules=config.DEFAULT_UNCOMPILED_MODULES) diff --git a/tensorflow/python/autograph/pyct/inspect_utils.py b/tensorflow/python/autograph/pyct/inspect_utils.py index a09d481003..6d5cced0ac 100644 --- a/tensorflow/python/autograph/pyct/inspect_utils.py +++ b/tensorflow/python/autograph/pyct/inspect_utils.py @@ -29,15 +29,25 @@ import six from tensorflow.python.util import tf_inspect +# These functions test negative for isinstance(*, types.BuiltinFunctionType) +# and inspect.isbuiltin, and are generally not visible in globals(). +SPECIAL_BUILTINS = { + 'dict': dict, + 'float': float, + 'int': int, + 'print': print, + 'range': range, + 'tuple': tuple +} + +if six.PY2: + SPECIAL_BUILTINS['xrange'] = xrange + + def isbuiltin(f): """Returns True if the argument is a built-in function.""" - # Note these return false for isinstance(f, types.BuiltinFunctionType) so we - # need to specifically check for them. - if f in (range, int, float): + if f in SPECIAL_BUILTINS.values(): return True - if six.PY2: - if f in (xrange,): - return True if isinstance(f, types.BuiltinFunctionType): return True if tf_inspect.isbuiltin(f): diff --git a/tensorflow/python/autograph/pyct/static_analysis/live_values.py b/tensorflow/python/autograph/pyct/static_analysis/live_values.py index dc363f9a47..e8e3d229be 100644 --- a/tensorflow/python/autograph/pyct/static_analysis/live_values.py +++ b/tensorflow/python/autograph/pyct/static_analysis/live_values.py @@ -24,21 +24,12 @@ from __future__ import division from __future__ import print_function import gast -import six from tensorflow.python.autograph.pyct import anno +from tensorflow.python.autograph.pyct import inspect_utils from tensorflow.python.autograph.pyct import transformer -# TODO(aqj): Do we need this? Do other builtins fail in similar ways -# See b/114389775 for a related bug in pyct -# These symbols are legal in Python, but don't appear in the namespace. -_SPECIAL_SYMBOLS = {'range': range, 'print': print} - -if six.PY2: - _SPECIAL_SYMBOLS['xrange'] = xrange - - class LiveValueResolver(transformer.Base): """Annotates nodes with live values.""" @@ -75,10 +66,11 @@ class LiveValueResolver(transformer.Base): # If the symbol value is for example a primitive, then it will not # have a name. pass - elif node.id in _SPECIAL_SYMBOLS: + elif node.id in inspect_utils.SPECIAL_BUILTINS: # Note: if the user redefined any of these symbols, then they would # be visible in the namespace and we would never reach this branch. - anno.setanno(node, 'live_val', _SPECIAL_SYMBOLS[node.id]) + anno.setanno( + node, 'live_val', inspect_utils.SPECIAL_BUILTINS[node.id]) else: pass # TODO(mdan): Should we raise an error here? -- GitLab From bda5267393295f51fef2cfc3ad86b23256640ff2 Mon Sep 17 00:00:00 2001 From: Eugene Zhulenev Date: Wed, 17 Oct 2018 16:47:24 -0700 Subject: [PATCH 0238/1825] [Grappler] Specialize indirect function calls. 1. Specialize functions called indirectly via the graph node attribute (PartitionedCall and StatefulPartitionedCall). 2. Do not copy node/function attributes for GrapplerFunctionItem instantiations, pass AttrSlice instead. PiperOrigin-RevId: 217613406 --- tensorflow/core/grappler/op_types.cc | 8 + tensorflow/core/grappler/op_types.h | 2 + .../grappler/optimizers/function_optimizer.cc | 282 ++++++++++++-- .../optimizers/function_optimizer_test.cc | 359 ++++++++++++++++-- .../optimizers/meta_optimizer_test.cc | 22 +- tensorflow/core/grappler/utils/functions.cc | 47 ++- tensorflow/core/grappler/utils/functions.h | 21 +- .../core/grappler/utils/functions_test.cc | 138 +++---- 8 files changed, 698 insertions(+), 181 deletions(-) diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index be7411019f..0317840a0a 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -300,6 +300,10 @@ bool IsPad(const NodeDef& node) { return op == "Pad" || op == "PadV2"; } +bool IsPartitionedCall(const NodeDef& node) { + return node.op() == "PartitionedCall"; +} + bool IsPlaceholder(const NodeDef& node) { const auto& op = node.op(); return op == "Placeholder" || op == "PlaceholderV2" || @@ -418,6 +422,10 @@ bool IsStackPopOp(const NodeDef& node) { return node.op() == "StackPop" || node.op() == "StackPopV2"; } +bool IsStatefulPartitionedCall(const NodeDef& node) { + return node.op() == "StatefulPartitionedCall"; +} + bool IsStopGradient(const NodeDef& node) { const auto& op = node.op(); return op == "StopGradient" || op == "PreventGradient"; diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index 92b62944b7..1a9103e744 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -98,6 +98,7 @@ bool IsNextIteration(const NodeDef& node); bool IsPack(const NodeDef& node); bool IsPad(const NodeDef& node); bool IsPack(const NodeDef& node); +bool IsPartitionedCall(const NodeDef& node); bool IsNeg(const NodeDef& node); bool IsNoOp(const NodeDef& node); bool IsNotEqual(const NodeDef& node); @@ -145,6 +146,7 @@ bool IsStackOp(const NodeDef& node); bool IsStackCloseOp(const NodeDef& node); bool IsStackPushOp(const NodeDef& node); bool IsStackPopOp(const NodeDef& node); +bool IsStatefulPartitionedCall(const NodeDef& node); bool IsStopGradient(const NodeDef& node); bool IsStridedSlice(const NodeDef& node); bool IsStridedSliceGrad(const NodeDef& node); diff --git a/tensorflow/core/grappler/optimizers/function_optimizer.cc b/tensorflow/core/grappler/optimizers/function_optimizer.cc index 7c35cc5f72..29f5180ded 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer.cc @@ -51,6 +51,9 @@ namespace { // Mark functions that were created as a result of function specialization. constexpr char kGrapplerSpecializedFuncAttr[] = "_GrapplerSpecializedFunc"; +// Name of the attribute that defines the function for indirect function calls. +constexpr char kFuncAttrName[] = "f"; + constexpr char kNoInlineAttr[] = "_noinline"; bool AttrIsTrue(const FunctionDef& func, const string& attr) { @@ -65,6 +68,43 @@ bool MarkedNoInline(const FunctionDef& func) { return AttrIsTrue(func, kNoInlineAttr); } +// There are two ways of calling a Tensorflow function: +// +// 1. Direct function call: node.op() is the name of the function. +// +// 2. Indirect function call: the function name is passed through a node +// attribute, and special Tensorflow kernels are responsible for calling the +// function through the FunctionLibraryRuntime. Example: PartitionedCallOp. + +// Check if func_node.op() matches the name in FunctionDef signature. +bool IsDirectFunctionCall(const FunctionDef& func, const NodeDef& func_node) { + return func_node.op() == func.signature().name(); +} + +// Check if func_node has function attribute with a function name matching +// FunctionDef signature. +bool IsIndirectFunctionCall(const FunctionDef& func, const NodeDef& func_node) { + auto* func_attr = AttrSlice(func_node).Find(kFuncAttrName); + return func_attr != nullptr && func_attr->has_func() && + func_attr->func().name() == func.signature().name(); +} + +AttrSlice FunctionInstantiationAttributes(const FunctionDef& func, + const NodeDef& func_node) { + if (IsDirectFunctionCall(func, func_node)) { + return AttrSlice(func_node); + + } else if (IsIndirectFunctionCall(func, func_node)) { + auto* func_attr = AttrSlice(func_node).Find(kFuncAttrName); + return AttrSlice(&func_attr->func().attr()); + + } else { + LOG(WARNING) << "Can't resolve function instantiation attributes: " + << SummarizeNodeDef(func_node); + return AttrSlice(); + } +} + // Find unique name for the specialized function. Collision can happen if // specialized function is instantiated for the nodes with the same name (e.g. // inside function body of two different functions). @@ -169,11 +209,16 @@ struct FunctionSpecializationSignature { struct FunctionSpecialization { string specialized_func_name; + // 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; // Control dependencies of pushed down const inputs have to be attached to // function caller node. gtl::FlatSet control_deps; + // Output tensors (ports) that consumed by other nodes in the graph or in a + // GrapplerItem fetch set. + gtl::FlatSet 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. @@ -517,32 +562,32 @@ Status PushDownConstInputs(const NodeDef& func_node, // Remove inputs that were pushed into the function body, and attach their // control dependencies to the function caller node. -void RemovePushedDownConstInputs(const gtl::FlatSet& const_inputs, - const gtl::FlatSet& control_deps, +void RemovePushedDownConstInputs(const FunctionSpecialization& specialization, NodeDef* specialized_func_node) { // Nothing to do if it was no const inputs to the function node. - if (const_inputs.empty()) return; + if (specialization.const_inputs.empty()) return; // Keep only non-const inputs. std::vector keep_inputs; const auto& inputs = specialized_func_node->input(); std::copy_if(inputs.begin(), inputs.end(), std::back_inserter(keep_inputs), [&](const string& input) { - return const_inputs.find(input) == const_inputs.end(); + return specialization.const_inputs.find(input) == + specialization.const_inputs.end(); }); specialized_func_node->clear_input(); for (const auto& keep : keep_inputs) specialized_func_node->add_input(keep); // Attach control dependencies of pushed down const input to the caller node. - if (!control_deps.empty()) { + if (!specialization.control_deps.empty()) { gtl::FlatSet existing_control_deps; for (const string& input : keep_inputs) { existing_control_deps.insert(AsControlDependency(NodeName(input))); } - for (const string& ctrl : control_deps) { + for (const string& ctrl : specialization.control_deps) { if (existing_control_deps.find(ctrl) == existing_control_deps.end()) { VLOG(3) << "Forward control dependency: input=" << ctrl; specialized_func_node->add_input(ctrl); @@ -551,10 +596,118 @@ void RemovePushedDownConstInputs(const gtl::FlatSet& const_inputs, } } +// Remove Tin type parameters for pushed down const inputs. +void RemovePushedDownConstInputTypes( + const FunctionSpecialization& specialization, const NodeDef& func_node, + NodeDef* specialized_func_node) { + // Nothing to do if it was no const inputs to the function node. + if (specialization.const_inputs.empty()) return; + + // Make sure that original function caller has Tin attribute. + const AttrValue* tin = AttrSlice(func_node).Find("Tin"); + if (tin == nullptr || !tin->has_list()) return; + + // Clear input types for the specialized node. + auto* attr = specialized_func_node->mutable_attr(); + (*attr)["Tin"].mutable_list()->clear_type(); + + // Keep types of non-const inputs. + for (int i = 0; i < func_node.input_size(); ++i) { + const string& input = func_node.input(i); + if (IsControlInput(input)) break; + + if (specialization.const_inputs.find(input) == + specialization.const_inputs.end()) { + DataType dt = tin->list().type(i); + (*attr)["Tin"].mutable_list()->add_type(dt); + } + } +} + +// Remove Tout type parameters for pruned function outputs. +void RemoveUnusedOutputsTypes(const FunctionSpecialization& specialization, + const NodeDef& func_node, + NodeDef* specialized_func_node) { + // Make sure that original function caller has Tout attribute. + const AttrValue* tout = AttrSlice(func_node).Find("Tout"); + if (tout == nullptr || !tout->has_list()) return; + + // Nothing to do if all outputs are active. + if (specialization.active_outputs.size() == tout->list().type_size()) return; + + // Clear input types for the specialized node. + auto* attr = specialized_func_node->mutable_attr(); + (*attr)["Tout"].mutable_list()->clear_type(); + + // Keep output types of active outputs only. + for (int i = 0; i < tout->list().type_size(); ++i) { + if (specialization.active_outputs.find(i) != + specialization.active_outputs.end()) { + DataType dt = tout->list().type(i); + (*attr)["Tout"].mutable_list()->add_type(dt); + } + } +} + +Status UpdateSpecializedFunctionCallSite(const FunctionDef& func, + const NodeDef& func_node, + const string& specialized_func_name, + NodeDef* specialized_func_node) { + if (IsDirectFunctionCall(func, func_node)) { + specialized_func_node->set_op(specialized_func_name); + + } else if (IsIndirectFunctionCall(func, func_node)) { + auto* attr = specialized_func_node->mutable_attr(); + (*attr)[kFuncAttrName].mutable_func()->set_name(specialized_func_name); + + } else { + return errors::InvalidArgument("Unknown function call site"); + } + + return Status::OK(); +} + +// Update a graph node created from the original function caller node, to the +// function specialization. Function specialization might change the number of +// inputs and outputs, so we have to make sure that graph node is updated +// accordingly. +Status UpdateSpecializedFunctionNode( + const FunctionDef& func, const NodeDef& func_node, + const FunctionSpecialization& specialization, + NodeDef* specialized_func_node) { + // Function called indirectly via custom kernel (e.g. PartitionedCallOp). + bool is_indirect_call = IsIndirectFunctionCall(func, func_node); + + // 1. Call the specialized function instead of original one. + TF_RETURN_IF_ERROR(UpdateSpecializedFunctionCallSite( + func, func_node, specialization.specialized_func_name, + specialized_func_node)); + + // 2. Remove inputs corresponding to the pushed down consts. + RemovePushedDownConstInputs(specialization, specialized_func_node); + + // 3. Update input types for the indirect function calls. + if (is_indirect_call) { + RemovePushedDownConstInputTypes(specialization, func_node, + specialized_func_node); + } + + // 4. Update output types for the indirect function call. It's unsafe to + // change the number of outputs for the fetch nodes, so we just skip them. + if (is_indirect_call && !specialization.is_in_fetch_set) { + RemoveUnusedOutputsTypes(specialization, func_node, specialized_func_node); + } + + // 5. Remove custom gradient annotation. + specialized_func_node->mutable_attr()->erase("_gradient_op_type"); + + return Status::OK(); +} + Status InitializeFunctionSpecializationSignature( const NodeDef& func_node, const FunctionDef& func, - const AttrValueMap& func_attr, const FunctionOptimizerContext& ctx, - FunctionSpecializationSignature* sig) { + const AttrSlice& func_instantiation_attr, + const FunctionOptimizerContext& ctx, FunctionSpecializationSignature* sig) { DCHECK(sig->const_inputs.empty()); DCHECK(sig->active_outputs.empty()); @@ -562,13 +715,14 @@ Status InitializeFunctionSpecializationSignature( sig->is_in_fetch_set = ctx.IsFetchNode(func_node.name()); sig->active_outputs = GetActiveOutputs(func_node, ctx); - TF_RETURN_IF_ERROR( - InstantiationTypeParameters(func, func_attr, &sig->type_parameters)); - TF_RETURN_IF_ERROR( - InstantiationBodyParameters(func, func_attr, &sig->body_parameters)); + TF_RETURN_IF_ERROR(InstantiationTypeParameters(func, func_instantiation_attr, + &sig->type_parameters)); + TF_RETURN_IF_ERROR(InstantiationBodyParameters(func, func_instantiation_attr, + &sig->body_parameters)); for (int i = 0; i < func_node.input_size(); ++i) { const string& input = func_node.input(i); + if (IsControlInput(input)) break; if (ctx.IsTrulyConst(input)) { sig->const_inputs.emplace(i, input); } @@ -581,15 +735,14 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, const int graph_def_version, FunctionOptimizerContext* ctx, GraphDef* optimized_graph) { - VLOG(2) << "Specialize function instantiation: " - << SummarizeNodeDef(func_node); + VLOG(2) << "Specialize function call: " << SummarizeNodeDef(func_node); - const std::unordered_map func_attr( - func_node.attr().begin(), func_node.attr().end()); + const AttrSlice func_instantiation_attr = + FunctionInstantiationAttributes(func, func_node); FunctionSpecializationSignature signature; TF_RETURN_IF_ERROR(InitializeFunctionSpecializationSignature( - func_node, func, func_attr, *ctx, &signature)); + func_node, func, func_instantiation_attr, *ctx, &signature)); // Check if function was already specialized for identical context. const FunctionSpecialization* already_specialized = @@ -603,11 +756,9 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, // Add a function call node for the specialized function. NodeDef* specialized_func_node = optimized_graph->add_node(); *specialized_func_node = func_node; - specialized_func_node->set_op(already_specialized->specialized_func_name); - RemovePushedDownConstInputs(already_specialized->const_inputs, - already_specialized->control_deps, - specialized_func_node); + TF_RETURN_IF_ERROR(UpdateSpecializedFunctionNode( + func, func_node, *already_specialized, specialized_func_node)); ctx->AddOutputMapping(specialized_func_node->name(), *already_specialized); @@ -620,8 +771,8 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, // Make a GrapplerFunctionItem and convert it back to FunctionDef after // pushing all constant inputs into the function body. GrapplerFunctionItem item; - TF_RETURN_IF_ERROR(MakeGrapplerFunctionItem(func, func_attr, flib, - graph_def_version, &item)); + TF_RETURN_IF_ERROR(MakeGrapplerFunctionItem(func, func_instantiation_attr, + flib, graph_def_version, &item)); // Push const inputs into the function body, and keep track of their control // dependencies. @@ -657,14 +808,14 @@ Status SpecializeFunction(const NodeDef& func_node, const FunctionDef& func, // Add a function call node for the specialized function. NodeDef* specialized_func_node = optimized_graph->add_node(); *specialized_func_node = func_node; - specialized_func_node->set_op(specialized_func_name); - - // Update specialized node to remove inputs for pushed down consts. - RemovePushedDownConstInputs(const_inputs, control_deps, - specialized_func_node); FunctionSpecialization func_specialization = { - specialized_func_name, const_inputs, control_deps, output_mapping}; + specialized_func_name, signature.is_in_fetch_set, const_inputs, + control_deps, signature.active_outputs, output_mapping}; + + TF_RETURN_IF_ERROR(UpdateSpecializedFunctionNode( + func, func_node, func_specialization, specialized_func_node)); + ctx->AddSpecializedFunction(signature, func_specialization); ctx->AddOutputMapping(specialized_func_node->name(), func_specialization); @@ -718,12 +869,19 @@ Status InlineFunction(const NodeDef& func_node, const FunctionDef& func, const int graph_def_version, GraphDef* optimized_graph) { VLOG(2) << "Inline function instantiation: " << SummarizeNodeDef(func_node); - const std::unordered_map func_attr( - func_node.attr().begin(), func_node.attr().end()); + // Specialized function call kernels might have behavior that is not + // representable in a graph (e.g. runtime ops device placing). + if (!IsDirectFunctionCall(func, func_node)) { + return errors::InvalidArgument("Can't inline indirect function call"); + } + + const AttrSlice func_instantiation_attr = + FunctionInstantiationAttributes(func, func_node); GrapplerFunctionItem item; - Status item_status = MakeGrapplerFunctionItem( - func, func_attr, ctx.function_library(), graph_def_version, &item); + Status item_status = MakeGrapplerFunctionItem(func, func_instantiation_attr, + ctx.function_library(), + graph_def_version, &item); if (!item_status.ok()) { return errors::InvalidArgument("Failed to inline function ", func_node.op(), @@ -919,7 +1077,7 @@ Status FunctionOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, bool specialize_func = options_.enable_function_specialization; for (const NodeDef& node : item.graph.node()) { - const string func_name = node.op(); + const string op_name = node.op(); // Each node optimization can modify optimized graph only by adding new // nodes, we can check node size to make sure that graph was not modified. @@ -945,8 +1103,11 @@ Status FunctionOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, } \ } while (0) - // 1. Inline symbolic gradients into the optimized graph. - if (func_name == "SymbolicGradient" && inline_gradients) { + // ---------------------------------------------------------------------- // + // 1. Inline symbolic gradients into the optimized graph. // + // ---------------------------------------------------------------------- // + + if (op_name == "SymbolicGradient" && inline_gradients) { // Inline symbolic gradients only if the corresponding function is inlined const auto* f_attr = gtl::FindOrNull(node.attr(), "f"); string f_name = f_attr != nullptr ? f_attr->func().name() : ""; @@ -957,11 +1118,14 @@ Status FunctionOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, } } - // 2. Check if a node op is a function call. - const FunctionDef* func = ctx.function_library().Find(func_name); + // ---------------------------------------------------------------------- // + // 2. Inline or specialize direct function calls. // + // ---------------------------------------------------------------------- // + + const FunctionDef* func = ctx.function_library().Find(op_name); if (func != nullptr) { // 2a. Inline it if it's allowed to do so. - if (inline_func && ctx.IsInlinedFunction(func_name)) { + if (inline_func && ctx.IsInlinedFunction(op_name)) { // Inline function body into the optimized graph} TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED( InlineFunction(node, *func, ctx, item.graph.versions().producer(), @@ -970,7 +1134,7 @@ Status FunctionOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, } // Do not specialize if function has custom gradient. - const string grad_func = ctx.function_library().FindGradient(func_name); + const string grad_func = ctx.function_library().FindGradient(op_name); // 2b. Specialize it to it's instantiation context if can't be inlined, // and it has something worth specializing. @@ -987,6 +1151,42 @@ Status FunctionOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, } } + // ---------------------------------------------------------------------- // + // 3. Specialize indirect function calls through the PartitionedCallOp. // + // ---------------------------------------------------------------------- // + + bool is_partitioned_call = + IsPartitionedCall(node) || IsStatefulPartitionedCall(node); + + // We can only specialize PartitionedCall ops. Inlining is not supported. + if (is_partitioned_call && specialize_func) { + const AttrValue* func_attr = AttrSlice(node).Find("f"); + string indirect_func_name = + (func_attr != nullptr && func_attr->has_func()) + ? func_attr->func().name() + : ""; + const FunctionDef* indirect_func = + ctx.function_library().Find(indirect_func_name); + + if (indirect_func != nullptr) { + // Do not specialize if function has custom gradient. + const string grad_func = + ctx.function_library().FindGradient(indirect_func_name); + + // Specialize it to it's instantiation context. + bool specialization_worthy = + IsParametrized(*indirect_func) || HasTrulyConstInputs(node, ctx) || + HasUnusedOutputs(node, *indirect_func, ctx); + if (grad_func.empty() && specialization_worthy) { + TF_SKIP_ERROR_IF_GRAPH_UNMODIFIED(SpecializeFunction( + node, *indirect_func, item.graph.versions().producer(), &ctx, + optimized_graph)); + continue; + } + } + } + + // ---------------------------------------------------------------------- // // If we reached this point, node was not handled by any of the stages // (inline, specialize), simply add a copy to the graph. add_node_copy(); @@ -1006,7 +1206,7 @@ Status FunctionOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, int from = mapping.first; int to = mapping.second; - // Find the output port corresponding to the old output position. + // Get the output port corresponding to the old output position. GraphView::OutputPort from_port = optimized_graph_view.GetOutputPort(node_name, from); diff --git a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc index a22f97800f..57a4cd0441 100644 --- a/tensorflow/core/grappler/optimizers/function_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/function_optimizer_test.cc @@ -494,49 +494,49 @@ TEST_F(FunctionOptimizerTest, InlineFunction_FunctionWithNestedFunctionCall) { int count = 0; for (const NodeDef& node : output.node()) { - if (node.name() == "square/inlined_inputs" && count++) { + if (node.name() == "square/inlined_inputs" && ++count) { EXPECT_EQ("IdentityN", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("a", node.input(0)); - } else if (node.name() == "square/x" && count++) { + } else if (node.name() == "square/x" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("square/inlined_inputs:0", node.input(0)); - } else if (node.name() == "square/output/inlined_inputs" && count++) { + } else if (node.name() == "square/output/inlined_inputs" && ++count) { EXPECT_EQ("IdentityN", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("square/x", node.input(0)); EXPECT_EQ("square/x", node.input(1)); - } else if (node.name() == "square/output/x" && count++) { + } else if (node.name() == "square/output/x" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("square/output/inlined_inputs:0", node.input(0)); - } else if (node.name() == "square/output/y" && count++) { + } else if (node.name() == "square/output/y" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("square/output/inlined_inputs:1", node.input(0)); - } else if (node.name() == "square/output/output" && count++) { + } else if (node.name() == "square/output/output" && ++count) { EXPECT_EQ("Mul", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("square/output/x", node.input(0)); EXPECT_EQ("square/output/y", node.input(1)); - } else if (node.name() == "square/output" && count++) { + } else if (node.name() == "square/output" && ++count) { EXPECT_EQ("IdentityN", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("square/output/output", node.input(0)); - } else if (node.name() == "square" && count++) { + } else if (node.name() == "square" && ++count) { EXPECT_EQ("IdentityN", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("square/output", node.input(0)); - } else if (node.name() == "outputs" && count++) { + } else if (node.name() == "outputs" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ(kDevice, node.device()); EXPECT_EQ(1, node.input_size()); @@ -699,7 +699,7 @@ TEST_F(FunctionOptimizerTest, InlineSymbolicGradient_NoInlineFunc) { CompareGraphs(item.graph, output); } -TEST_F(FunctionOptimizerTest, SpecializeFunction_XTimesTwo) { +TEST_F(FunctionOptimizerTest, SpecializeFunctionXTimesTwo) { using test::function::NDef; FunctionOptimizer optimizer(RewriterConfig::DEFAULT); @@ -729,7 +729,7 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_XTimesTwo) { // And 'y' node is calling specialized function. int count = 0; for (const NodeDef& node : output.node()) { - if (node.name() == "y" && count++) { + if (node.name() == "y" && ++count) { EXPECT_EQ("XTimesTwo_specialized_for_y", node.op()); } } @@ -746,7 +746,70 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_XTimesTwo) { test::ExpectTensorEqual(tensors_expected[0], tensors[0]); } -TEST_F(FunctionOptimizerTest, SpecializeFunction_PushDownConstInput) { +TEST_F(FunctionOptimizerTest, SpecializeIndirectFunctionXTimesTwo) { + using test::function::NDef; + using FDH = FunctionDefHelper; + + FunctionOptimizer optimizer(RewriterConfig::DEFAULT); + + // Mark XTimesTwo as noinline. + FunctionDef x_times_two = test::function::XTimesTwo(); + (*x_times_two.mutable_attr())["_noinline"].set_b(true); + std::vector function_library = {x_times_two}; + + // Tensorflow graph: + // y = PartitionedCall[f=XTimesTwo, Tin=[DT_FLOAT], Tout=[DT_FLOAT]](x) + GrapplerItem item; + item.graph = test::function::GDef( + {NDef("x", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + NDef("y", "PartitionedCall", {"x"}, + {{"Tin", DataTypeSlice{DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT}}, + {"f", FDH::FunctionRef("XTimesTwo", {{"T", DT_FLOAT}})}}, + kDevice), + NDef("z", "Identity", {"y"}, {{"T", DT_FLOAT}}, kDevice)}, + function_library); + + GraphDef output; + TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &output)); + + // Make sure that specialized function was added to the library and original + // function was removed. + EXPECT_EQ(1, output.library().function_size()); + EXPECT_EQ("XTimesTwo_specialized_for_y", + output.library().function(0).signature().name()); + + // And 'y' node is calling specialized function. + int count = 0; + for (const NodeDef& node : output.node()) { + if (node.name() == "y" && ++count) { + EXPECT_EQ("PartitionedCall", node.op()); + auto& func = AttrSlice(node).Find("f")->func(); + // Function calls into the specialized function. + EXPECT_EQ("XTimesTwo_specialized_for_y", func.name()); + // And input/output types stay the same. + auto& tin = AttrSlice(node).Find("Tin")->list(); + auto& tout = AttrSlice(node).Find("Tout")->list(); + ASSERT_EQ(1, tin.type_size()); + ASSERT_EQ(1, tout.type_size()); + EXPECT_EQ(DT_FLOAT, tin.type(0)); + EXPECT_EQ(DT_FLOAT, tout.type(0)); + } + } + EXPECT_EQ(1, count); + + // And that graph evaluation yields the same result. + Tensor pi = test::AsScalar(3.14f); + item.fetch = {"z"}; + item.feed.emplace_back("x", pi); + + auto tensors_expected = EvaluateFetchNodes(item); + GrapplerItem optimized(item, std::move(output)); + auto tensors = EvaluateFetchNodes(optimized); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); +} + +TEST_F(FunctionOptimizerTest, SpecializeFunctionPushDownConstInput) { using test::function::NDef; FunctionOptimizer optimizer(RewriterConfig::DEFAULT); @@ -788,7 +851,7 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_PushDownConstInput) { // And 'y' node has control dependencies of a pushed down const node. int count = 0; for (const NodeDef& node : output.node()) { - if (node.name() == "y" && count++) { + if (node.name() == "y" && ++count) { ASSERT_EQ(2, node.input_size()); EXPECT_EQ("x", node.input(0)); EXPECT_EQ("^init", node.input(1)); @@ -807,6 +870,84 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_PushDownConstInput) { test::ExpectTensorEqual(tensors_expected[0], tensors[0]); } +TEST_F(FunctionOptimizerTest, SpecializeIndirectFunctionPushDownConstInput) { + using test::function::NDef; + using FDH = FunctionDefHelper; + + FunctionOptimizer optimizer(RewriterConfig::DEFAULT); + + FunctionDef mul_func = FunctionDefHelper::Create( + "MyMul", {"x:T", "y:T"}, {"z:T"}, {"T: {float, double}"}, + {{{"output"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z", "output:z:0"}}); + + // Mark MyMul as noinline. + (*mul_func.mutable_attr())["_noinline"].set_b(true); + std::vector function_library = {mul_func}; + + const Tensor kTwo = test::AsScalar(2.0); + + // Tensorflow graph: + // y = PartitionedCall[Tin=[DT_FLOAT], Tout=[DT_FLOAT], f=MyMul](x, two) + GrapplerItem item; + item.graph = test::function::GDef( + {NDef("x", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + NDef("init", "NoOp", {}, {}, kDevice), + NDef("two", "Const", {"^init", "^x"}, + {{"dtype", DT_FLOAT}, {"value", kTwo}}, kDevice), + NDef("y", "PartitionedCall", {"x", "two"}, + {{"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT}}, + {"f", FDH::FunctionRef("MyMul", {{"T", DT_FLOAT}})}}, + kDevice), + NDef("z", "Identity", {"y"}, {{"T", DT_FLOAT}}, kDevice)}, + function_library); + + GraphDef output; + TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &output)); + + // Make sure that specialized function was added to the library and original + // function was removed. + ASSERT_EQ(1, output.library().function_size()); + + const FunctionDef& specialized = output.library().function(0); + EXPECT_EQ("MyMul_specialized_for_y", specialized.signature().name()); + EXPECT_EQ(1, specialized.signature().input_arg_size()); + + // And 'y' node has control dependencies of a pushed down const node. + int count = 0; + for (const NodeDef& node : output.node()) { + if (node.name() == "y" && ++count) { + EXPECT_EQ("PartitionedCall", node.op()); + ASSERT_EQ(2, node.input_size()); + EXPECT_EQ("x", node.input(0)); + EXPECT_EQ("^init", node.input(1)); + // Function calls into the specialized function. + auto& func = AttrSlice(node).Find("f")->func(); + EXPECT_EQ("MyMul_specialized_for_y", func.name()); + // And input/output type lists were updated. + auto& tin = AttrSlice(node).Find("Tin")->list(); + auto& tout = AttrSlice(node).Find("Tout")->list(); + ASSERT_EQ(1, tin.type_size()); + ASSERT_EQ(1, tout.type_size()); + EXPECT_EQ(DT_FLOAT, tin.type(0)); + EXPECT_EQ(DT_FLOAT, tout.type(0)); + } + } + ASSERT_EQ(1, count); + + // And that graph evaluation yields the same result. + Tensor pi = test::AsScalar(3.14f); + item.fetch = {"z"}; + item.feed.emplace_back("x", pi); + + auto tensors_expected = EvaluateFetchNodes(item); + GrapplerItem optimized(item, std::move(output)); + auto tensors = EvaluateFetchNodes(optimized); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); +} + TEST_F(FunctionOptimizerTest, SpecializeFunction_OncePerUniqueContext) { using test::function::NDef; @@ -870,31 +1011,31 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_OncePerUniqueContext) { // And graph nodes calling specialized functions. int count = 0; for (const NodeDef& node : output.node()) { - if (node.name() == "mul_1" && count++) { + if (node.name() == "mul_1" && ++count) { EXPECT_EQ("MyMul_specialized_for_mul_1", node.op()); ASSERT_EQ(2, node.input_size()); EXPECT_EQ("xf", node.input(0)); EXPECT_EQ("yf", node.input(1)); - } else if (node.name() == "mul_2" && count++) { + } else if (node.name() == "mul_2" && ++count) { EXPECT_EQ("MyMul_specialized_for_mul_1", node.op()); ASSERT_EQ(2, node.input_size()); EXPECT_EQ("yf", node.input(0)); EXPECT_EQ("xf", node.input(1)); - } else if (node.name() == "mul_3" && count++) { + } else if (node.name() == "mul_3" && ++count) { EXPECT_EQ("MyMul_specialized_for_mul_3", node.op()); ASSERT_EQ(2, node.input_size()); EXPECT_EQ("xi", node.input(0)); EXPECT_EQ("yi", node.input(1)); - } else if (node.name() == "mul_4" && count++) { + } else if (node.name() == "mul_4" && ++count) { EXPECT_EQ("MyMul_specialized_for_mul_4", node.op()); ASSERT_EQ(2, node.input_size()); EXPECT_EQ("xf", node.input(0)); EXPECT_EQ("^init", node.input(1)); - } else if (node.name() == "mul_5" && count++) { + } else if (node.name() == "mul_5" && ++count) { EXPECT_EQ("MyMul_specialized_for_mul_4", node.op()); ASSERT_EQ(3, node.input_size()); EXPECT_EQ("yf", node.input(0)); @@ -902,7 +1043,7 @@ TEST_F(FunctionOptimizerTest, SpecializeFunction_OncePerUniqueContext) { gtl::FlatSet actual_ctrl = {node.input(1), node.input(2)}; EXPECT_EQ(expected_ctrl, actual_ctrl); - } else if (node.name() == "mul_6" && count++) { + } else if (node.name() == "mul_6" && ++count) { EXPECT_EQ("MyMul_specialized_for_mul_6", node.op()); ASSERT_EQ(2, node.input_size()); EXPECT_EQ("xf", node.input(0)); @@ -991,28 +1132,188 @@ TEST_F(FunctionOptimizerTest, SpecializeFunctionForUsedOutputTensors) { int found = 0; for (const NodeDef& node : output.node()) { // All function caller nodes must be specialized. - if (node.name() == "fn1" && found++) { + if (node.name() == "fn1" && ++found) { EXPECT_EQ("MyFunc_specialized_for_fn1", node.op()); - } else if (node.name() == "fn2" && found++) { + } else if (node.name() == "fn2" && ++found) { EXPECT_EQ("MyFunc_specialized_for_fn2", node.op()); - } else if (node.name() == "fn3" && found++) { + } else if (node.name() == "fn3" && ++found) { EXPECT_EQ("MyFunc_specialized_for_fn3", node.op()); - } else if (node.name() == "fn4" && found++) { + } else if (node.name() == "fn4" && ++found) { EXPECT_EQ("MyFunc_specialized_for_fn4", node.op()); - } else if (node.name() == "fn5" && found++) { + } else if (node.name() == "fn5" && ++found) { EXPECT_EQ("MyFunc_specialized_for_fn5", node.op()); - } else if (node.name() == "fn6" && found++) { + } else if (node.name() == "fn6" && ++found) { EXPECT_EQ("MyFunc_specialized_for_fn6", node.op()); } // And all consumers of specialized function nodes must be mapped to new // output ports. - if (node.name() == "use_fn3_1" && found++) { + if (node.name() == "use_fn3_1" && ++found) { + EXPECT_EQ("fn3:0", node.input(0)); + } else if (node.name() == "use_fn4_2" && ++found) { + EXPECT_EQ("fn4:0", node.input(0)); + } else if (node.name() == "use_fn5_0" && ++found) { + EXPECT_EQ("fn5:0", node.input(0)); + } else if (node.name() == "use_fn5_2" && ++found) { + EXPECT_EQ("fn5:1", node.input(0)); + } + } + EXPECT_EQ(10, found); + + // And that graph evaluation yields the same result. + Tensor pi = test::AsScalar(3.14f); + item.fetch = {"use_fn1_0", "use_fn1_1", "use_fn1_2", "use_fn2_0", + "use_fn3_1", "use_fn4_2", "use_fn5_0", "use_fn5_2"}; + item.feed = {{"xf", pi}, {"yf", pi}}; + + auto tensors_expected = EvaluateFetchNodes(item); + GrapplerItem optimized(item, std::move(output)); + auto tensors = EvaluateFetchNodes(optimized); + + ASSERT_EQ(tensors_expected.size(), tensors.size()); + for (int i = 0; i < item.fetch.size(); ++i) { + test::ExpectTensorEqual(tensors_expected[i], tensors[i]); + } +} + +TEST_F(FunctionOptimizerTest, SpecializeIndirectFunctionForUsedOutputTensors) { + using test::function::NDef; + using FDH = FunctionDefHelper; + + FunctionOptimizer optimizer(RewriterConfig::DEFAULT); + + // MyFunc computes x*y three times and has three output values. + FunctionDef my_func = FunctionDefHelper::Create( + "MyFunc", {"x:T", "y:T"}, {"z1:T", "z2:T", "z3:T"}, {"T: {float, int32}"}, + {{{"output1"}, "Mul", {"x", "y"}, {{"T", "$T"}}}, + {{"output2"}, "Mul", {"x", "y"}, {{"T", "$T"}}}, + {{"output3"}, "Mul", {"x", "y"}, {{"T", "$T"}}}}, + /* Mapping between function returns and function node outputs. */ + {{"z1", "output1:z:0"}, {"z2", "output2:z:0"}, {"z3", "output3:z:0"}}); + (*my_func.mutable_attr())["_noinline"].set_b(true); + std::vector function_library = {my_func}; + + GrapplerItem item; + item.graph = test::function::GDef( + {NDef("init", "NoOp", {}, {}, kDevice), + + // Float placeholders. + NDef("xf", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + NDef("yf", "Placeholder", {}, {{"dtype", DT_FLOAT}}, kDevice), + + // Specialization #1: DT_FLOAT type parameter. All outputs used. + NDef("fn1", "PartitionedCall", {"xf", "yf"}, + {{"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT, DT_FLOAT, DT_FLOAT}}, + {"f", FDH::FunctionRef("MyFunc", {{"T", DT_FLOAT}})}}, + kDevice), + NDef("use_fn1_0", "Identity", {"fn1:0"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn1_1", "Identity", {"fn1:1"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn1_2", "Identity", {"fn1:2"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #2: DT_FLOAT type parameter. Only first output used. + NDef("fn2", "PartitionedCall", {"xf", "yf"}, + {{"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT, DT_FLOAT, DT_FLOAT}}, + {"f", FDH::FunctionRef("MyFunc", {{"T", DT_FLOAT}})}}, + kDevice), + NDef("use_fn2_0", "Identity", {"fn2:0"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #3: DT_FLOAT type parameter. Only second output used. + NDef("fn3", "PartitionedCall", {"xf", "yf"}, + {{"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT, DT_FLOAT, DT_FLOAT}}, + {"f", FDH::FunctionRef("MyFunc", {{"T", DT_FLOAT}})}}, + kDevice), + NDef("use_fn3_1", "Identity", {"fn3:1"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #4: DT_FLOAT type parameter. Only last output used. + NDef("fn4", "PartitionedCall", {"xf", "yf"}, + {{"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT, DT_FLOAT, DT_FLOAT}}, + {"f", FDH::FunctionRef("MyFunc", {{"T", DT_FLOAT}})}}, + kDevice), + NDef("use_fn4_2", "Identity", {"fn4:2"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #5: DT_FLOAT type parameter. First and last outputs. + NDef("fn5", "PartitionedCall", {"xf", "yf"}, + {{"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT, DT_FLOAT, DT_FLOAT}}, + {"f", FDH::FunctionRef("MyFunc", {{"T", DT_FLOAT}})}}, + kDevice), + NDef("use_fn5_0", "Identity", {"fn5:0"}, {{"T", DT_FLOAT}}, kDevice), + NDef("use_fn5_2", "Identity", {"fn5:2"}, {{"T", DT_FLOAT}}, kDevice), + + // Specialization #6: DT_FLOAT type parameter. Outputs not used. + // Check that function optimizer do not fail. In practice it should be + // pruned from the graph before passing to function optimizer. + NDef("fn6", "PartitionedCall", {"xf", "yf"}, + {{"Tin", DataTypeSlice{DT_FLOAT, DT_FLOAT}}, + {"Tout", DataTypeSlice{DT_FLOAT, DT_FLOAT, DT_FLOAT}}, + {"f", FDH::FunctionRef("MyFunc", {{"T", DT_FLOAT}})}}, + kDevice)}, + function_library); + + GraphDef output; + TF_EXPECT_OK(optimizer.Optimize(nullptr, item, &output)); + + // Make sure that MyFunc was specialized once per unique context. + EXPECT_EQ(6, output.library().function_size()); + + // And graph nodes calling specialized functions. + int found = 0; + for (const NodeDef& node : output.node()) { + // All function caller nodes must be specialized. + if (node.name() == "fn1" && ++found) { + auto& func = AttrSlice(node).Find("f")->func(); + auto& tout = AttrSlice(node).Find("Tout")->list(); + EXPECT_EQ("PartitionedCall", node.op()); + EXPECT_EQ("MyFunc_specialized_for_fn1", func.name()); + ASSERT_EQ(3, tout.type_size()); + + } else if (node.name() == "fn2" && ++found) { + auto& func = AttrSlice(node).Find("f")->func(); + auto& tout = AttrSlice(node).Find("Tout")->list(); + EXPECT_EQ("PartitionedCall", node.op()); + EXPECT_EQ("MyFunc_specialized_for_fn2", func.name()); + ASSERT_EQ(1, tout.type_size()); + + } else if (node.name() == "fn3" && ++found) { + auto& func = AttrSlice(node).Find("f")->func(); + auto& tout = AttrSlice(node).Find("Tout")->list(); + EXPECT_EQ("PartitionedCall", node.op()); + EXPECT_EQ("MyFunc_specialized_for_fn3", func.name()); + ASSERT_EQ(1, tout.type_size()); + + } else if (node.name() == "fn4" && ++found) { + auto& func = AttrSlice(node).Find("f")->func(); + auto& tout = AttrSlice(node).Find("Tout")->list(); + EXPECT_EQ("PartitionedCall", node.op()); + EXPECT_EQ("MyFunc_specialized_for_fn4", func.name()); + ASSERT_EQ(1, tout.type_size()); + + } else if (node.name() == "fn5" && ++found) { + auto& func = AttrSlice(node).Find("f")->func(); + auto& tout = AttrSlice(node).Find("Tout")->list(); + EXPECT_EQ("PartitionedCall", node.op()); + EXPECT_EQ("MyFunc_specialized_for_fn5", func.name()); + ASSERT_EQ(2, tout.type_size()); + + } else if (node.name() == "fn6" && ++found) { + auto& func = AttrSlice(node).Find("f")->func(); + auto& tout = AttrSlice(node).Find("Tout")->list(); + EXPECT_EQ("PartitionedCall", node.op()); + EXPECT_EQ("MyFunc_specialized_for_fn6", func.name()); + ASSERT_EQ(0, tout.type_size()); + } + // And all consumers of specialized function nodes must be mapped to new + // output ports. + if (node.name() == "use_fn3_1" && ++found) { EXPECT_EQ("fn3:0", node.input(0)); - } else if (node.name() == "use_fn4_2" && found++) { + } else if (node.name() == "use_fn4_2" && ++found) { EXPECT_EQ("fn4:0", node.input(0)); - } else if (node.name() == "use_fn5_0" && found++) { + } else if (node.name() == "use_fn5_0" && ++found) { EXPECT_EQ("fn5:0", node.input(0)); - } else if (node.name() == "use_fn5_2" && found++) { + } else if (node.name() == "use_fn5_2" && ++found) { EXPECT_EQ("fn5:1", node.input(0)); } } diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc b/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc index e15b9e12f8..7172f8071e 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer_test.cc @@ -313,9 +313,9 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { // Graph should call optimized function. int count = 0; for (const NodeDef& node : output.node()) { - if (node.name() == "square" && count++) { + if (node.name() == "square" && ++count) { EXPECT_EQ("MySquare_specialized_for_square", node.op()); - } else if (node.name() == "quadratic" && count++) { + } else if (node.name() == "quadratic" && ++count) { EXPECT_EQ("MyQuadratic_specialized_for_quadratic", node.op()); } } @@ -324,9 +324,9 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { // Specialized MySquare should call specialized functions. count = 0; for (const NodeDef& node : optimized_func_0->node_def()) { - if (node.name() == "square" && count++) { + if (node.name() == "square" && ++count) { EXPECT_EQ(optimized_2, node.op()); - } else if (node.name() == "quadratic" && count++) { + } else if (node.name() == "quadratic" && ++count) { EXPECT_EQ(optimized_3, node.op()); } } @@ -339,25 +339,25 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibrary) { for (const FunctionDef* optimized_func : optimized_funcs) { count = 0; for (const NodeDef& node : optimized_func->node_def()) { - if (node.name() == "my_mul/inlined_inputs" && count++) { + if (node.name() == "my_mul/inlined_inputs" && ++count) { EXPECT_EQ("IdentityN", node.op()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("x:0", node.input(0)); EXPECT_EQ("x:0", node.input(1)); - } else if (node.name() == "my_mul/x" && count++) { + } else if (node.name() == "my_mul/x" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("my_mul/inlined_inputs:output:0", node.input(0)); - } else if (node.name() == "my_mul/y" && count++) { + } else if (node.name() == "my_mul/y" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("my_mul/inlined_inputs:output:1", node.input(0)); - } else if (node.name() == "my_mul/mul" && count++) { + } else if (node.name() == "my_mul/mul" && ++count) { EXPECT_EQ("Mul", node.op()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("my_mul/x:output:0", node.input(0)); EXPECT_EQ("my_mul/y:output:0", node.input(1)); - } else if (node.name() == "my_mul" && count++) { + } else if (node.name() == "my_mul" && ++count) { EXPECT_EQ("IdentityN", node.op()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("my_mul/mul:z:0", node.input(0)); @@ -446,9 +446,9 @@ TEST_F(MetaOptimizerTest, OptimizeFunctionLibraryPruneFunctionBody) { // Graph should call optimized function. int count = 0; for (const NodeDef& node : output.node()) { - if (node.name() == "fn1" && count++) { + if (node.name() == "fn1" && ++count) { EXPECT_EQ(optimized_fn1, node.op()); - } else if (node.name() == "fn2" && count++) { + } else if (node.name() == "fn2" && ++count) { EXPECT_EQ(optimized_fn2, node.op()); } } diff --git a/tensorflow/core/grappler/utils/functions.cc b/tensorflow/core/grappler/utils/functions.cc index bfb5a2ad84..05f1d1067f 100644 --- a/tensorflow/core/grappler/utils/functions.cc +++ b/tensorflow/core/grappler/utils/functions.cc @@ -57,14 +57,14 @@ Status RegisterFunctionBodyOutputs(const FunctionLibraryDefinition& flib, // Replace the placeholder attribute values with the values specified in // instantiation attributes. Status ResolveFunctionBodyNodeAttrPlaceholders( - const AttrValueMap& func_instantiation_attr, NodeDef* node) { + const AttrSlice& func_instantiation_attr, NodeDef* node) { for (auto& attr : *node->mutable_attr()) { const string& placeholder = attr.second.placeholder(); if (placeholder.empty()) continue; - auto it = func_instantiation_attr.find(placeholder); - if (it != func_instantiation_attr.end()) { - attr.second = it->second; + const AttrValue* attr_value = func_instantiation_attr.Find(placeholder); + if (attr_value) { + attr.second = *attr_value; } else { return errors::InvalidArgument("Can't resolve placeholder: ", placeholder); @@ -277,15 +277,15 @@ Status GrapplerFunctionConnectivity::AsFunctionDefNode( Status GrapplerFunctionItemInstantiation::GetTypeAttr( const string& type_attr_name, DataType* data_type) const { - auto it = func_instantiation_attr_->find(type_attr_name); - if (it == func_instantiation_attr_->end()) { + const AttrValue* type_attr = func_instantiation_attr_.Find(type_attr_name); + if (type_attr == nullptr) { return errors::InvalidArgument("Type attribute ", type_attr_name, " is not defined"); - } else if (it->second.type() == DT_INVALID) { + } else if (type_attr->type() == DT_INVALID) { return errors::InvalidArgument("Type attribute ", type_attr_name, " is not defined with a valid type"); } else { - *data_type = it->second.type(); + *data_type = type_attr->type(); } return Status::OK(); } @@ -307,7 +307,7 @@ Status GrapplerFunctionItemInstantiation::GetArgType( } GrapplerFunctionItem::GrapplerFunctionItem( - string func_name, string description, AttrValueMap func_attr, + string func_name, string description, AttrSlice func_attr, std::vector input_arg_expansions, std::vector output_arg_expansions, std::vector keep_nodes, const int graph_def_version, @@ -375,9 +375,7 @@ const std::size_t GrapplerFunctionItem::output_size() const { return output_arg_expansions_.size(); } -const AttrValueMap& GrapplerFunctionItem::func_attr() const { - return func_attr_; -} +const AttrSlice& GrapplerFunctionItem::func_attr() const { return func_attr_; } const GraphDef& GrapplerFunctionItem::function_body() const { return graph; } @@ -418,13 +416,13 @@ bool IsParametrized(const FunctionDef& func) { } Status InstantiationTypeParameters( - const FunctionDef& func, const AttrValueMap& func_instantiation_attr, + const FunctionDef& func, const AttrSlice& func_instantiation_attr, std::unordered_map* type_parameters) { if (!type_parameters->empty()) { return errors::InvalidArgument("Type parameters output map must be empty"); } - GrapplerFunctionItemInstantiation instantiation(&func_instantiation_attr); + GrapplerFunctionItemInstantiation instantiation(func_instantiation_attr); const auto resolve_type_attr = [&](const OpDef::ArgDef& arg) { // Check if it's unknown and unresolved type. @@ -446,7 +444,7 @@ Status InstantiationTypeParameters( } Status InstantiationBodyParameters( - const FunctionDef& func, const AttrValueMap& func_instantiation_attr, + const FunctionDef& func, const AttrSlice& func_instantiation_attr, std::unordered_map* body_parameters) { if (!body_parameters->empty()) { return errors::InvalidArgument("Body parameters output map must be empty"); @@ -461,9 +459,10 @@ Status InstantiationBodyParameters( continue; } - auto it = func_instantiation_attr.find(placeholder); - if (it != func_instantiation_attr.end()) { - body_parameters->insert({placeholder, it->second}); + const AttrValue* placeholder_value = + func_instantiation_attr.Find(placeholder); + if (placeholder_value) { + body_parameters->insert({placeholder, *placeholder_value}); } else { return errors::InvalidArgument("Can't resolve placeholder: ", placeholder); @@ -475,7 +474,7 @@ Status InstantiationBodyParameters( } Status MakeGrapplerFunctionItem(const FunctionDef& func, - const AttrValueMap& func_instantiation_attr, + const AttrSlice& func_instantiation_attr, const FunctionLibraryDefinition& flib, const int graph_def_version, GrapplerFunctionItem* item) { @@ -495,7 +494,7 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, } // Helper methods to lookup function instantiation attributes - GrapplerFunctionItemInstantiation instantiation(&func_instantiation_attr); + GrapplerFunctionItemInstantiation instantiation(func_instantiation_attr); // Mapping from FunctionDef input format (name[:output][:position]) to // GraphDef input format (name[:position]) @@ -602,9 +601,9 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, *item = GrapplerFunctionItem( /*func_name=*/signature.name(), /*description=*/signature.description(), - /*func_attr=*/AttrValueMap(func.attr().begin(), func.attr().end()), - std::move(inputs), std::move(outputs), std::move(keep_nodes), - graph_def_version, is_stateful, std::move(function_body)); + /*func_attr=*/AttrSlice(&func.attr()), std::move(inputs), + std::move(outputs), std::move(keep_nodes), graph_def_version, is_stateful, + std::move(function_body)); return Status::OK(); } @@ -612,7 +611,7 @@ Status MakeGrapplerFunctionItem(const FunctionDef& func, const FunctionLibraryDefinition& flib, const int graph_def_version, GrapplerFunctionItem* item) { - return MakeGrapplerFunctionItem(func, AttrValueMap(), flib, graph_def_version, + return MakeGrapplerFunctionItem(func, AttrSlice(), flib, graph_def_version, item); } diff --git a/tensorflow/core/grappler/utils/functions.h b/tensorflow/core/grappler/utils/functions.h index dc8c3f1d11..39dddda876 100644 --- a/tensorflow/core/grappler/utils/functions.h +++ b/tensorflow/core/grappler/utils/functions.h @@ -30,8 +30,6 @@ limitations under the License. namespace tensorflow { namespace grappler { -using AttrValueMap = std::unordered_map; - // 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. @@ -118,8 +116,7 @@ class GrapplerFunctionConnectivity { // a function. class GrapplerFunctionItemInstantiation { public: - explicit GrapplerFunctionItemInstantiation( - const AttrValueMap* func_instantiation_attr) + explicit GrapplerFunctionItemInstantiation(AttrSlice func_instantiation_attr) : func_instantiation_attr_(func_instantiation_attr) {} // Get DataType from attributes by name. Return error if attribute is missing, @@ -131,7 +128,7 @@ class GrapplerFunctionItemInstantiation { Status GetArgType(const OpDef::ArgDef& arg, DataType* data_type) const; private: - const AttrValueMap* func_instantiation_attr_; // do not own + const AttrSlice func_instantiation_attr_; // do not own }; // A special case of GrapplerItem, constructed from a TensorFlow Function. @@ -139,7 +136,7 @@ class GrapplerFunctionItem : public GrapplerItem { public: GrapplerFunctionItem() = default; GrapplerFunctionItem(string func_name, string description, - AttrValueMap func_attr, + AttrSlice func_attr, std::vector input_arg_expansions, std::vector output_arg_expansions, std::vector keep_nodes, int graph_def_version, @@ -157,7 +154,7 @@ class GrapplerFunctionItem : public GrapplerItem { const OutputArgExpansion& output(int i) const; const std::size_t output_size() const; - const AttrValueMap& func_attr() const; + const AttrSlice& func_attr() const; const GraphDef& function_body() const; GraphDef& mutable_function_body(); @@ -173,8 +170,8 @@ class GrapplerFunctionItem : public GrapplerItem { std::vector>* output_mapping); string description_; - AttrValueMap func_attr_; // Attributes specific to function definition that - // produced this item (FuncDef.attr field). + AttrSlice func_attr_; // Attributes specific to function definition that + // produced this item (FuncDef.attr field). std::vector input_arg_expansions_; std::vector output_arg_expansions_; @@ -199,14 +196,14 @@ bool IsParametrized(const FunctionDef& func); // Resolve function instantiation type parameters from the attributes of the // caller node. Return error if type can't be resolved. Status InstantiationTypeParameters( - const FunctionDef& func, const AttrValueMap& func_instantiation_attr, + const FunctionDef& func, const AttrSlice& func_instantiation_attr, std::unordered_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 AttrValueMap& func_instantiation_attr, + const FunctionDef& func, const AttrSlice& func_instantiation_attr, std::unordered_map* body_parameters); // Register GrapplerFunctionItem input arg expansion and function body outputs @@ -234,7 +231,7 @@ Status RemoveUnusedOutputs(const gtl::FlatSet& active_outputs, // instantiation attributes (caller node attributes). Returns error if the given // function def cannot be converted (e.g. not all attributes are defined). Status MakeGrapplerFunctionItem(const FunctionDef& func, - const AttrValueMap& func_instantiation_attr, + const AttrSlice& func_instantiation_attr, const FunctionLibraryDefinition& flib, int graph_def_version, GrapplerFunctionItem* item); diff --git a/tensorflow/core/grappler/utils/functions_test.cc b/tensorflow/core/grappler/utils/functions_test.cc index b51f2781b8..edc273557b 100644 --- a/tensorflow/core/grappler/utils/functions_test.cc +++ b/tensorflow/core/grappler/utils/functions_test.cc @@ -69,15 +69,15 @@ TEST_F(FunctionsTest, InstantiationParameters) { /* Mapping between function returns and function node outputs. */ {{"x", "cx:output:0"}, {"y", "cy:output:0"}}); - std::unordered_map func_instantiation_attr; + protobuf::Map func_instantiation_attr; func_instantiation_attr["key"].set_s("key-value"); func_instantiation_attr["A"].set_type(DT_FLOAT); func_instantiation_attr["B"].set_type(DT_INT32); func_instantiation_attr["C"].set_type(DT_DOUBLE); std::unordered_map type_parameters; - TF_EXPECT_OK(InstantiationTypeParameters(func, func_instantiation_attr, - &type_parameters)); + TF_EXPECT_OK(InstantiationTypeParameters( + func, AttrSlice(&func_instantiation_attr), &type_parameters)); ASSERT_EQ(3, type_parameters.size()); EXPECT_EQ(DT_FLOAT, type_parameters["A"]); @@ -85,8 +85,8 @@ TEST_F(FunctionsTest, InstantiationParameters) { EXPECT_EQ(DT_DOUBLE, type_parameters["C"]); std::unordered_map body_parameters; - TF_EXPECT_OK(InstantiationBodyParameters(func, func_instantiation_attr, - &body_parameters)); + TF_EXPECT_OK(InstantiationBodyParameters( + func, AttrSlice(&func_instantiation_attr), &body_parameters)); ASSERT_EQ(1, body_parameters.size()); EXPECT_EQ("key-value", body_parameters["key"].s()); @@ -235,13 +235,14 @@ TEST_F(FunctionsTest, FromSimpleFunctionDef) { {{"y"}, "Mul", {"x", "scale"}, {{"T", "$T"}}}, }); - std::unordered_map func_attr; - func_attr["T"].set_type(DT_FLOAT); + protobuf::Map func_instantiation_attr; + func_instantiation_attr["T"].set_type(DT_FLOAT); FunctionLibraryDefinition flib(OpRegistry::Global(), FunctionDefLibrary()); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ("XTimesTwo", item.id); EXPECT_EQ(4, item.function_body().node_size()); @@ -256,19 +257,19 @@ TEST_F(FunctionsTest, FromSimpleFunctionDef) { int count = 0; for (const NodeDef &node : item.function_body().node()) { - if (node.name() == "x" && count++) { + if (node.name() == "x" && ++count) { EXPECT_EQ("Placeholder", node.op()); EXPECT_EQ(DT_FLOAT, node.attr().at("dtype").type()); EXPECT_EQ(0, node.input_size()); - } else if (node.name() == "two" && count++) { + } else if (node.name() == "two" && ++count) { EXPECT_EQ("Const", node.op()); EXPECT_EQ(0, node.input_size()); - } else if (node.name() == "scale" && count++) { + } else if (node.name() == "scale" && ++count) { EXPECT_EQ("Cast", node.op()); EXPECT_EQ(DT_FLOAT, node.attr().at("DstT").type()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("two", node.input(0)); - } else if (node.name() == "y" && count++) { + } else if (node.name() == "y" && ++count) { EXPECT_EQ("Mul", node.op()); EXPECT_EQ(DT_FLOAT, node.attr().at("T").type()); EXPECT_EQ(2, node.input_size()); @@ -311,13 +312,14 @@ TEST_F(FunctionsTest, FromFunctionDefWithMultiOutputNodes) { // Nodes nodes); - std::unordered_map func_attr; - func_attr["T"].set_type(DT_FLOAT); + protobuf::Map func_instantiation_attr; + func_instantiation_attr["T"].set_type(DT_FLOAT); FunctionLibraryDefinition flib(OpRegistry::Global(), FunctionDefLibrary()); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ("SubGrad", item.id); EXPECT_EQ(12, item.function_body().node_size()); @@ -338,17 +340,17 @@ TEST_F(FunctionsTest, FromFunctionDefWithMultiOutputNodes) { EXPECT_EQ("Placeholder", node.op()); EXPECT_EQ(DT_FLOAT, node.attr().at("dtype").type()); EXPECT_EQ(0, node.input_size()); - } else if (node.name() == "rx" && count++) { + } else if (node.name() == "rx" && ++count) { EXPECT_EQ("BroadcastGradientArgs", node.op()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("sx", node.input(0)); EXPECT_EQ("sy", node.input(1)); - } else if (node.name() == "sum_gx" && count++) { + } else if (node.name() == "sum_gx" && ++count) { EXPECT_EQ("Sum", node.op()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("gx", node.input(0)); EXPECT_EQ("rx", node.input(1)); - } else if (node.name() == "sum_gy" && count++) { + } else if (node.name() == "sum_gy" && ++count) { EXPECT_EQ("Sum", node.op()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("gy", node.input(0)); @@ -394,12 +396,13 @@ TEST_F(FunctionsTest, FromFunctionDefWithNestedFuncs) { // Output Mapping {{"o", "o:z:0"}}); - std::unordered_map func_attr; - func_attr["T"].set_type(DT_FLOAT); + protobuf::Map func_instantiation_attr; + func_instantiation_attr["T"].set_type(DT_FLOAT); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); int count = 0; for (const NodeDef &node : item.function_body().node()) { @@ -408,29 +411,29 @@ TEST_F(FunctionsTest, FromFunctionDefWithNestedFuncs) { EXPECT_EQ("Placeholder", node.op()); EXPECT_EQ(DT_FLOAT, node.attr().at("dtype").type()); EXPECT_EQ(0, node.input_size()); - } else if (node.name() == "a0" && count++) { + } else if (node.name() == "a0" && ++count) { EXPECT_EQ("Swap", node.op()); EXPECT_EQ(3, node.input_size()); EXPECT_EQ("x", node.input(0)); EXPECT_EQ("y", node.input(1)); EXPECT_EQ("^x2", node.input(2)); - } else if (node.name() == "a1" && count++) { + } else if (node.name() == "a1" && ++count) { EXPECT_EQ("Swap", node.op()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("a0", node.input(0)); EXPECT_EQ("a0:1", node.input(1)); - } else if (node.name() == "x2" && count++) { + } else if (node.name() == "x2" && ++count) { EXPECT_EQ("Mul", node.op()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("x", node.input(0)); EXPECT_EQ("x", node.input(1)); - } else if (node.name() == "y2" && count++) { + } else if (node.name() == "y2" && ++count) { EXPECT_EQ("Mul", node.op()); EXPECT_EQ(3, node.input_size()); EXPECT_EQ("y", node.input(0)); EXPECT_EQ("y", node.input(1)); EXPECT_EQ("^a1", node.input(2)); - } else if (node.name() == "o" && count++) { + } else if (node.name() == "o" && ++count) { EXPECT_EQ("Add", node.op()); EXPECT_EQ(2, node.input_size()); EXPECT_EQ("x2", node.input(0)); @@ -456,27 +459,28 @@ TEST_F(FunctionsTest, FromFunctionDefWithOutputMappings) { // Mapping {{"out", "Exp:y:0"}}); - std::unordered_map func_attr; + protobuf::Map func_instantiation_attr; FunctionLibraryDefinition flib(OpRegistry::Global(), FunctionDefLibrary()); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ(1, item.output_size()); EXPECT_EQ("Exp", item.output(0).output_tensors[0]); int count = 0; for (const NodeDef &node : item.function_body().node()) { - if (node.name() == "in" && count++) { + if (node.name() == "in" && ++count) { EXPECT_EQ("Placeholder", node.op()); EXPECT_EQ(DT_FLOAT, node.attr().at("dtype").type()); EXPECT_EQ(0, node.input_size()); - } else if (node.name() == "Linear_func" && count++) { + } else if (node.name() == "Linear_func" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("in", node.input(0)); - } else if (node.name() == "Exp" && count++) { + } else if (node.name() == "Exp" && ++count) { EXPECT_EQ("Exp", node.op()); EXPECT_EQ(1, node.input_size()); EXPECT_EQ("Linear_func", node.input(0)); @@ -500,12 +504,13 @@ TEST_F(FunctionsTest, FromFunctionDefWithInputForwarding) { // Mapping {{"out0", "in0"}}); - std::unordered_map func_attr; + protobuf::Map func_instantiation_attr; FunctionLibraryDefinition flib(OpRegistry::Global(), FunctionDefLibrary()); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ("ForwardInputs", item.id); EXPECT_EQ(5, item.function_body().node_size()); @@ -546,13 +551,14 @@ TEST_F(FunctionsTest, FromFunctionDefWithoutInput) { {{{"two"}, "Const", {}, {{"value", kTwo}, {"dtype", DT_INT64}}}, {{"o"}, "Cast", {"two"}, {{"SrcT", DT_INT64}, {"DstT", "$T"}}}}); - std::unordered_map func_attr; - func_attr["T"].set_type(DT_FLOAT); + protobuf::Map func_instantiation_attr; + func_instantiation_attr["T"].set_type(DT_FLOAT); FunctionLibraryDefinition flib(OpRegistry::Global(), FunctionDefLibrary()); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ(0, item.input_size()); EXPECT_EQ(1, item.output_size()); @@ -586,13 +592,14 @@ TEST_F(FunctionsTest, MakeFunctionDef) { {{"y"}, "Mul", {"x", "scale"}, {{"T", "$T"}}}, }); - std::unordered_map func_attr; - func_attr["T"].set_type(DT_FLOAT); + protobuf::Map func_instantiation_attr; + func_instantiation_attr["T"].set_type(DT_FLOAT); FunctionLibraryDefinition flib(OpRegistry::Global(), FunctionDefLibrary()); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); FunctionDef specialized; TF_EXPECT_OK(MakeFunctionDef(item, flib, &specialized)); @@ -606,9 +613,9 @@ TEST_F(FunctionsTest, MakeFunctionDef) { // Function body specialized for instantiation types int count = 0; for (const NodeDef &node : specialized.node_def()) { - if (node.name() == "scale" && count++) { + if (node.name() == "scale" && ++count) { EXPECT_EQ(DT_FLOAT, node.attr().at("DstT").type()); - } else if (node.name() == "y" && count++) { + } else if (node.name() == "y" && ++count) { EXPECT_EQ("Mul", node.op()); EXPECT_EQ("x:0", node.input(0)); EXPECT_EQ("scale:y:0", node.input(1)); @@ -625,13 +632,14 @@ TEST_F(FunctionsTest, ReplaceInputWithConst) { /* Mapping between function returns and function node outputs. */ {{"z", "output:z:0"}}); - std::unordered_map func_attr; - func_attr["T"].set_type(DT_FLOAT); + protobuf::Map func_instantiation_attr; + func_instantiation_attr["T"].set_type(DT_FLOAT); FunctionLibraryDefinition flib(OpRegistry::Global(), FunctionDefLibrary()); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); EXPECT_EQ(2, item.input_size()); EXPECT_EQ(1, item.output_size()); @@ -679,13 +687,13 @@ TEST_F(FunctionsTest, ReplaceInputWithConst) { // Check that graph has const nodes pushed into function body. int count = 0; for (const NodeDef &node : specialized.node_def()) { - if (node.name() == "x" && count++) { + if (node.name() == "x" && ++count) { EXPECT_EQ("Const", node.op()); EXPECT_EQ("const_input_x", node.attr().at("Tag").s()); - } else if (node.name() == "y" && count++) { + } else if (node.name() == "y" && ++count) { EXPECT_EQ("Const", node.op()); EXPECT_EQ("const_input_y", node.attr().at("Tag").s()); - } else if (node.name() == "output" && count++) { + } else if (node.name() == "output" && ++count) { EXPECT_EQ("Mul", node.op()); EXPECT_EQ("x:output:0", node.input(0)); EXPECT_EQ("y:output:0", node.input(1)); @@ -713,8 +721,8 @@ TEST_F(FunctionsTest, SwapFunctionBodyAndMakeFunctionDef) { {/* pass input to output through identity */ NDef("output", "Identity", {"x"}, {{"T", "float"}})}); - std::unordered_map func_attr; - func_attr["T"].set_type(DT_FLOAT); + protobuf::Map func_instantiation_attr; + func_instantiation_attr["T"].set_type(DT_FLOAT); FunctionDefLibrary lib_def; *lib_def.add_function() = func; @@ -722,8 +730,9 @@ TEST_F(FunctionsTest, SwapFunctionBodyAndMakeFunctionDef) { FunctionLibraryDefinition flib(OpRegistry::Global(), lib_def); GrapplerFunctionItem item; - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); // Replace function body with identity function item.SwapFunctionBody(std::move(id_func_body)); @@ -733,7 +742,7 @@ 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() == "output" && ++count) { EXPECT_EQ("Identity", node.op()); EXPECT_EQ("x:0", node.input(0)); } @@ -762,10 +771,11 @@ TEST_F(FunctionsTest, FunctionDefGrapplerFunctionItemRoundTrip) { FunctionLibraryDefinition flib(OpRegistry::Global(), FunctionDefLibrary()); GrapplerFunctionItem item; - std::unordered_map func_attr; - func_attr["T"].set_type(DT_INT32); - TF_EXPECT_OK(MakeGrapplerFunctionItem(func, func_attr, flib, - TF_GRAPH_DEF_VERSION, &item)); + protobuf::Map func_instantiation_attr; + func_instantiation_attr["T"].set_type(DT_INT32); + TF_EXPECT_OK(MakeGrapplerFunctionItem(func, + AttrSlice(&func_instantiation_attr), + flib, TF_GRAPH_DEF_VERSION, &item)); FunctionDef func2; TF_EXPECT_OK(MakeFunctionDef(item, flib, &func2)); -- GitLab From a54150fe9571c713e45a3f2828fbaf5d18dda3d3 Mon Sep 17 00:00:00 2001 From: Yuanzhong Xu Date: Wed, 17 Oct 2018 17:00:31 -0700 Subject: [PATCH 0239/1825] [XLA] Chnage param_test configs. PiperOrigin-RevId: 217615354 --- tensorflow/compiler/xla/tests/BUILD | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD index 8a0ae33042..3ad6960b4e 100644 --- a/tensorflow/compiler/xla/tests/BUILD +++ b/tensorflow/compiler/xla/tests/BUILD @@ -476,7 +476,9 @@ xla_test( name = "params_test", srcs = ["params_test.cc"], shard_count = 30, - tags = ["optonly"], + tags = [ + "optonly", + ], deps = [ "//tensorflow/compiler/xla:array2d", "//tensorflow/compiler/xla:literal", -- GitLab From be409cac81eaadc135ebe07593a3d26420b2ef8d Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 17:09:23 -0700 Subject: [PATCH 0240/1825] During error conditions, we were currently putting an entire NodeDef information in the user facing error message. This makes it very hard to read and understand the error messages. We now just put the name in the message, and put the NodeDef information in the logs. PiperOrigin-RevId: 217616833 --- tensorflow/compiler/jit/deadness_analysis.cc | 4 +- tensorflow/compiler/tf2xla/graph_compiler.cc | 2 +- tensorflow/compiler/tf2xla/tf2xla.cc | 2 +- .../compiler/tf2xla/xla_compilation_device.cc | 2 +- .../compiler/tf2xla/xla_compiler_test.cc | 6 ++- .../core/common_runtime/eager/execute.cc | 10 ++--- tensorflow/core/common_runtime/executor.cc | 4 +- .../core/common_runtime/lower_if_while.cc | 6 +-- .../core/common_runtime/memory_types.cc | 13 +++--- .../core/distributed_runtime/graph_mgr.cc | 2 +- tensorflow/core/framework/function.cc | 9 +++-- tensorflow/core/framework/graph_def_util.cc | 2 +- tensorflow/core/framework/node_def_util.cc | 40 ++++++++++--------- tensorflow/core/framework/op_kernel.cc | 8 ++-- tensorflow/core/graph/graph.cc | 2 +- tensorflow/core/graph/node_builder.cc | 2 +- tensorflow/python/client/session.py | 2 +- 17 files changed, 62 insertions(+), 54 deletions(-) diff --git a/tensorflow/compiler/jit/deadness_analysis.cc b/tensorflow/compiler/jit/deadness_analysis.cc index b7ae7fbeb3..e29da8500f 100644 --- a/tensorflow/compiler/jit/deadness_analysis.cc +++ b/tensorflow/compiler/jit/deadness_analysis.cc @@ -696,8 +696,8 @@ Status CreateMultipleNextIterationInputsError(Node* merge) { } } return errors::InvalidArgument( - "Multiple NextIteration inputs to merge node ", SummarizeNode(*merge), - ": \n", absl::StrJoin(backedges, "\n"), + "Multiple NextIteration inputs to merge node ", + FormatNodeForError(*merge), ": \n", absl::StrJoin(backedges, "\n"), "\nMerge nodes can have at most one incoming NextIteration edge."); } diff --git a/tensorflow/compiler/tf2xla/graph_compiler.cc b/tensorflow/compiler/tf2xla/graph_compiler.cc index c019a28e89..706ed4f5bb 100644 --- a/tensorflow/compiler/tf2xla/graph_compiler.cc +++ b/tensorflow/compiler/tf2xla/graph_compiler.cc @@ -164,7 +164,7 @@ Status GraphCompiler::Compile() { outputs[o] = op_context.release_output(o); if (outputs[o].tensor == nullptr) { return errors::Internal("Missing xla_context ", o, "-th output from ", - SummarizeNode(*n)); + FormatNodeForError(*n)); } } } diff --git a/tensorflow/compiler/tf2xla/tf2xla.cc b/tensorflow/compiler/tf2xla/tf2xla.cc index b22d53805d..9fac16a970 100644 --- a/tensorflow/compiler/tf2xla/tf2xla.cc +++ b/tensorflow/compiler/tf2xla/tf2xla.cc @@ -218,7 +218,7 @@ Status CollectArgNodes(const Graph& graph, std::vector* arg_nodes) { const Node* dup = insert_result.first->second; return errors::InvalidArgument( "Multiple ", kArgOp, " nodes with index ", index, ", ", - n->DebugString(), " and ", dup->DebugString()); + FormatNodeForError(*n), " and ", FormatNodeForError(*dup)); } } } diff --git a/tensorflow/compiler/tf2xla/xla_compilation_device.cc b/tensorflow/compiler/tf2xla/xla_compilation_device.cc index 7f860500c7..cb7843850c 100644 --- a/tensorflow/compiler/tf2xla/xla_compilation_device.cc +++ b/tensorflow/compiler/tf2xla/xla_compilation_device.cc @@ -92,7 +92,7 @@ Allocator* XlaCompilationDevice::GetAllocator(AllocatorAttributes attr) { void XlaCompilationDevice::Compute(OpKernel* op_kernel, OpKernelContext* context) { VLOG(4) << "XlaCompilationDevice::Compute " - << SummarizeNodeDef(op_kernel->def()); + << FormatNodeDefForError(op_kernel->def()); auto* b = XlaContext::Get(context).builder(); xla::OpMetadata metadata; metadata.set_op_type(op_kernel->type_string()); diff --git a/tensorflow/compiler/tf2xla/xla_compiler_test.cc b/tensorflow/compiler/tf2xla/xla_compiler_test.cc index 72b17d04fc..2cb8b3331c 100644 --- a/tensorflow/compiler/tf2xla/xla_compiler_test.cc +++ b/tensorflow/compiler/tf2xla/xla_compiler_test.cc @@ -354,8 +354,10 @@ TEST_F(XlaCompilerTest, HasSaneErrorOnNonCompileTimeConstantInputToReshape) { EXPECT_TRUE( absl::StrContains(status.error_message(), "depends on a parameter")) << status.error_message(); - EXPECT_TRUE( - absl::StrContains(status.error_message(), "[[{{node C}} = Reshape")) + EXPECT_TRUE(absl::StrContains(status.error_message(), "{{node C}}")) + << status.error_message(); + EXPECT_TRUE(absl::StrContains(status.error_message(), + "must be a compile-time constant")) << status.error_message(); } diff --git a/tensorflow/core/common_runtime/eager/execute.cc b/tensorflow/core/common_runtime/eager/execute.cc index 51402c12f0..81e0cd1b71 100644 --- a/tensorflow/core/common_runtime/eager/execute.cc +++ b/tensorflow/core/common_runtime/eager/execute.cc @@ -197,10 +197,10 @@ Status SelectDevice(const NodeDef& ndef, EagerContext* ctx, Device** device) { TF_RETURN_IF_ERROR(SupportedDeviceTypesForNode( ctx->prioritized_device_type_list(), ndef, &final_devices)); if (final_devices.empty()) { - return errors::Internal( - "Could not find valid device for node.\nNode: ", SummarizeNodeDef(ndef), - "\nAll kernels registered for op ", ndef.op(), " :\n", - KernelsRegisteredForOp(ndef.op())); + return errors::Internal("Could not find valid device for node.\nNode: ", + FormatNodeDefForError(ndef), + "\nAll kernels registered for op ", ndef.op(), + " :\n", KernelsRegisteredForOp(ndef.op())); } for (Device* d : *ctx->devices()) { if (d->device_type() == final_devices[0].type_string()) { @@ -209,7 +209,7 @@ Status SelectDevice(const NodeDef& ndef, EagerContext* ctx, Device** device) { } } return errors::Unknown("Could not find a device for node ", - SummarizeNodeDef(ndef)); + FormatNodeDefForError(ndef)); } Status GetOutputDTypes(EagerOperation* op, DataTypeVector* output_dtypes) { diff --git a/tensorflow/core/common_runtime/executor.cc b/tensorflow/core/common_runtime/executor.cc index eb69d1991c..f5c6a5c669 100644 --- a/tensorflow/core/common_runtime/executor.cc +++ b/tensorflow/core/common_runtime/executor.cc @@ -1976,7 +1976,7 @@ Status ExecutorState::ProcessOutputs(const NodeItem& item, OpKernelContext* ctx, // tensor value at i-th output. if (!IsSwitch(node) && !IsRecv(node)) { s.Update(errors::Internal("Missing ", i, "-th output from ", - SummarizeNode(*node))); + FormatNodeForError(*node))); } } else { Entry* out = &((*outputs)[i]); @@ -2030,7 +2030,7 @@ Status ExecutorState::ProcessOutputs(const NodeItem& item, OpKernelContext* ctx, DataTypeString(dtype), " does not match declared output type ", DataTypeString(item.output_type(i)), - " for node ", SummarizeNode(*node))); + " for node ", FormatNodeForError(*node))); } } if (!val.is_ref()) { diff --git a/tensorflow/core/common_runtime/lower_if_while.cc b/tensorflow/core/common_runtime/lower_if_while.cc index c1db575b04..ce8d99d6f1 100644 --- a/tensorflow/core/common_runtime/lower_if_while.cc +++ b/tensorflow/core/common_runtime/lower_if_while.cc @@ -79,9 +79,9 @@ Status LowerIfWhilePass::Run(const GraphOptimizationPassOptions& options) { TF_RETURN_IF_ERROR(RewriteWhileNode(n, g, *flib)); } else { return errors::Internal( - "Node:", n->name(), " of type ", n->type_string(), " has '", - LowerIfWhilePass::kLowerUsingSwitchMergeAttr, - "' attr set but it does not support lowering.\n", n->DebugString()); + "Node ", FormatNodeForError(*n), " of type ", n->type_string(), + " has '", LowerIfWhilePass::kLowerUsingSwitchMergeAttr, + "' attr set but it does not support lowering.\n"); } } } diff --git a/tensorflow/core/common_runtime/memory_types.cc b/tensorflow/core/common_runtime/memory_types.cc index 116750fbfd..f2534b7dc3 100644 --- a/tensorflow/core/common_runtime/memory_types.cc +++ b/tensorflow/core/common_runtime/memory_types.cc @@ -96,11 +96,12 @@ Status ValidateMemoryTypes(const DeviceType& device_type, const Graph* g) { if (sm == dm) { return Status::OK(); } - return errors::Internal( - "Memory type mismatch (", sm, " ", dm, - ") between :", e->src()->id(), ":", e->src_output(), " and ", - e->dst()->id(), ":", e->dst_input(), " : from ", - e->src()->DebugString(), " to ", e->dst()->DebugString()); + return errors::Internal("Memory type mismatch (", sm, " ", dm, + ") between :", e->src()->id(), ":", + e->src_output(), " and ", e->dst()->id(), ":", + e->dst_input(), " : from ", + FormatNodeForError(*e->src()), " to ", + FormatNodeForError(*e->dst())); }); } @@ -209,7 +210,7 @@ Status MemoryTypeForOutput(const DeviceType& device_type, const Graph* g, &inp_mvec, &out_mvec)); if (out_mvec.size() <= index) { return errors::Internal("Trying to get the memory type for ", index, - "'th output of node ", n->DebugString(), + "'th output of node ", FormatNodeForError(*n), " that has only ", out_mvec.size(), " outputs"); } *memory_type = out_mvec[index]; diff --git a/tensorflow/core/distributed_runtime/graph_mgr.cc b/tensorflow/core/distributed_runtime/graph_mgr.cc index 3361819e43..3944668028 100644 --- a/tensorflow/core/distributed_runtime/graph_mgr.cc +++ b/tensorflow/core/distributed_runtime/graph_mgr.cc @@ -90,7 +90,7 @@ static Status ValidateGraphDefForDevices(const GraphDef& gdef) { for (const auto& ndef : gdef.node()) { if (!DeviceNameUtils::ParseFullName(ndef.device(), &parsed)) { return errors::InvalidArgument("Missing device name in: ", - SummarizeNodeDef(ndef)); + FormatNodeDefForError(ndef)); } } return Status::OK(); diff --git a/tensorflow/core/framework/function.cc b/tensorflow/core/framework/function.cc index 4ad6fd00da..abd0930ca9 100644 --- a/tensorflow/core/framework/function.cc +++ b/tensorflow/core/framework/function.cc @@ -238,7 +238,8 @@ class FunctionInstantiationHelper { const auto* item = GetItemOrNull(input_name); if (item == nullptr) { return errors::InvalidArgument( - "input ", input_name, " is not found: ", SummarizeNodeDef(fnode)); + "input ", input_name, + " is not found: ", FormatNodeDefForError(fnode)); } if (item->dtypes.size() > dtypes.size() - j) { return errors::InvalidArgument("Input ", input_name, " too long for ", @@ -677,7 +678,8 @@ Status InstantiateFunction(const FunctionDef& fdef, AttrSlice attr_values, s = helper.BuildNodeOutputIndex(fdef.node_def(i), AttrSlice(&node_attrs[i]), result->nodes.size() + i); if (!s.ok()) { - errors::AppendToMessage(&s, "In ", SummarizeNodeDef(fdef.node_def(i))); + errors::AppendToMessage(&s, "In ", + FormatNodeDefForError(fdef.node_def(i))); return s; } } @@ -685,7 +687,8 @@ Status InstantiateFunction(const FunctionDef& fdef, AttrSlice attr_values, for (int i = 0; i < fdef.node_def_size(); ++i) { s = helper.InstantiateNode(fdef.node_def(i), AttrSlice(&node_attrs[i])); if (!s.ok()) { - errors::AppendToMessage(&s, "In ", SummarizeNodeDef(fdef.node_def(i))); + errors::AppendToMessage(&s, "In ", + FormatNodeDefForError(fdef.node_def(i))); return s; } } diff --git a/tensorflow/core/framework/graph_def_util.cc b/tensorflow/core/framework/graph_def_util.cc index f7539d37be..56102db30e 100644 --- a/tensorflow/core/framework/graph_def_util.cc +++ b/tensorflow/core/framework/graph_def_util.cc @@ -103,7 +103,7 @@ static Status RemoveNewDefaultAttrsFromNodeDef( return errors::InvalidArgument( "Attr '", attr.first, "' missing in producer's OpDef: ", SummarizeOpDef(*producer_op_def), - " but found in node: ", SummarizeNodeDef(*node_def)); + " but found in node: ", FormatNodeDefForError(*node_def)); } // ...and it has the same value as the default in producer, if (producer_attr_def->has_default_value() && diff --git a/tensorflow/core/framework/node_def_util.cc b/tensorflow/core/framework/node_def_util.cc index 43ac1d0ada..c69e067511 100644 --- a/tensorflow/core/framework/node_def_util.cc +++ b/tensorflow/core/framework/node_def_util.cc @@ -86,8 +86,8 @@ string AttrSlice::SummarizeNode() const { string SummarizeNode(const Node& node) { return SummarizeNodeDef(node.def()); } string SummarizeNodeDef(const NodeDef& node_def) { - string ret = strings::StrCat(FormatNodeDefForError(node_def), " = ", - node_def.op(), "["); + string ret = strings::StrCat(errors::FormatNodeNameForError(node_def.name()), + " = ", node_def.op(), "["); strings::StrAppend(&ret, SummarizeAttrsHelper(node_def, node_def.device())); strings::StrAppend(&ret, "]("); @@ -107,6 +107,7 @@ string FormatNodeForError(const Node& node) { } string FormatNodeDefForError(const NodeDef& node_def) { + LOG(ERROR) << "Error in the node: " << SummarizeNodeDef(node_def); return errors::FormatNodeNameForError(node_def.name()); } @@ -419,9 +420,9 @@ Status NumOutputsForNode(const NodeDef& node_def, const OpDef& op_def, Status ValidateNodeDef(const NodeDef& node_def, const OpDef& op_def) { if (node_def.op() != op_def.name()) { - return errors::InvalidArgument("NodeDef op '", node_def.op(), - "' does not match ", SummarizeOpDef(op_def), - "; NodeDef: ", SummarizeNodeDef(node_def)); + return errors::InvalidArgument( + "NodeDef op '", node_def.op(), "' does not match ", + SummarizeOpDef(op_def), "; NodeDef: ", FormatNodeDefForError(node_def)); } bool seen_control = false; @@ -431,14 +432,14 @@ Status ValidateNodeDef(const NodeDef& node_def, const OpDef& op_def) { if (str_util::StartsWith(input, "^")) { seen_control = true; if (input.find(':') != string::npos) { - return errors::InvalidArgument( - "Control input '", input, - "' must not have ':' in NodeDef: ", SummarizeNodeDef(node_def)); + return errors::InvalidArgument("Control input '", input, + "' must not have ':' in NodeDef: ", + FormatNodeDefForError(node_def)); } } else if (seen_control) { - return errors::InvalidArgument( - "Non-control input '", input, - "' after control input in NodeDef: ", SummarizeNodeDef(node_def)); + return errors::InvalidArgument("Non-control input '", input, + "' after control input in NodeDef: ", + FormatNodeDefForError(node_def)); } else { ++num_inputs; } @@ -468,13 +469,14 @@ Status ValidateNodeDef(const NodeDef& node_def, const OpDef& op_def) { // the binary producing it. return errors::InvalidArgument( "NodeDef mentions attr '", attr.first, "' not in ", - SummarizeOpDef(op_def), "; NodeDef: ", SummarizeNodeDef(node_def), + SummarizeOpDef(op_def), + "; NodeDef: ", FormatNodeDefForError(node_def), ". (Check whether your GraphDef-interpreting binary is up to date " "with your GraphDef-generating binary.)."); } TF_RETURN_WITH_CONTEXT_IF_ERROR( ValidateAttrValue(attr.second, *iter->second), - "; NodeDef: ", SummarizeNodeDef(node_def), "; ", + "; NodeDef: ", FormatNodeDefForError(node_def), "; ", SummarizeOpDef(op_def)); // Keep track of which attr names have (not) been found in the NodeDef. op_attrs.erase(iter); @@ -487,10 +489,10 @@ Status ValidateNodeDef(const NodeDef& node_def, const OpDef& op_def) { if (!attrs.empty()) strings::StrAppend(&attrs, "', '"); strings::StrAppend(&attrs, attr_pair.first); } - return errors::InvalidArgument("NodeDef missing attr", - op_attrs.size() == 1 ? " '" : "s '", attrs, - "' from ", SummarizeOpDef(op_def), - "; NodeDef: ", SummarizeNodeDef(node_def)); + return errors::InvalidArgument( + "NodeDef missing attr", op_attrs.size() == 1 ? " '" : "s '", attrs, + "' from ", SummarizeOpDef(op_def), + "; NodeDef: ", FormatNodeDefForError(node_def)); } // Validate the number of inputs. @@ -501,7 +503,7 @@ Status ValidateNodeDef(const NodeDef& node_def, const OpDef& op_def) { return errors::InvalidArgument( "NodeDef expected inputs '", DataTypeVectorString(inputs), "' do not match ", num_inputs, " inputs specified; ", - SummarizeOpDef(op_def), "; NodeDef: ", SummarizeNodeDef(node_def)); + SummarizeOpDef(op_def), "; NodeDef: ", FormatNodeDefForError(node_def)); } return Status::OK(); @@ -657,7 +659,7 @@ Status ValidateExternalNodeDefSyntax(const NodeDef& node_def) { Status AttachDef(const Status& status, const NodeDef& node_def) { Status ret = status; errors::AppendToMessage( - &ret, strings::StrCat(" [[", SummarizeNodeDef(node_def), "]]")); + &ret, strings::StrCat(" [[", FormatNodeDefForError(node_def), "]]")); return ret; } diff --git a/tensorflow/core/framework/op_kernel.cc b/tensorflow/core/framework/op_kernel.cc index c26214c857..2b2f8ffa83 100644 --- a/tensorflow/core/framework/op_kernel.cc +++ b/tensorflow/core/framework/op_kernel.cc @@ -1033,7 +1033,7 @@ Status FindKernelRegistration(const DeviceType& device_type, if (*reg != nullptr) { return errors::InvalidArgument( "Multiple OpKernel registrations match NodeDef '", - SummarizeNodeDef(node_def), "': '", + FormatNodeDefForError(node_def), "': '", ProtoShortDebugString((*reg)->def), "' and '", ProtoShortDebugString(iter->second.def), "'"); } @@ -1058,7 +1058,7 @@ Status FindKernelDef(const DeviceType& device_type, const NodeDef& node_def, Status s = errors::NotFound( "No registered '", node_def.op(), "' OpKernel for ", DeviceTypeString(device_type), " devices compatible with node ", - SummarizeNodeDef(node_def)); + FormatNodeDefForError(node_def)); if (was_attr_mismatch) { errors::AppendToMessage( &s, " (OpKernel was found, but attributes didn't match)"); @@ -1184,7 +1184,7 @@ Status CreateOpKernel(DeviceType device_type, DeviceBase* device, s.Update(errors::NotFound("No registered '", node_def.op(), "' OpKernel for ", DeviceTypeString(device_type), " devices compatible with node ", - SummarizeNodeDef(node_def))); + FormatNodeDefForError(node_def))); if (was_attr_mismatch) { errors::AppendToMessage( &s, " (OpKernel was found, but attributes didn't match)"); @@ -1199,7 +1199,7 @@ Status CreateOpKernel(DeviceType device_type, DeviceBase* device, DataTypeVector outputs; s.Update(InOutTypesForNode(node_def, *op_def, &inputs, &outputs)); if (!s.ok()) { - errors::AppendToMessage(&s, " for node: ", SummarizeNodeDef(node_def)); + errors::AppendToMessage(&s, " for node: ", FormatNodeDefForError(node_def)); return s; } diff --git a/tensorflow/core/graph/graph.cc b/tensorflow/core/graph/graph.cc index bc0a6ae346..db565e8170 100644 --- a/tensorflow/core/graph/graph.cc +++ b/tensorflow/core/graph/graph.cc @@ -529,7 +529,7 @@ Status Graph::UpdateEdge(Node* new_src, int new_src_index, Node* dst, const Edge* e = FindEdge(dst, dst_index); if (e == nullptr) { return errors::InvalidArgument("Couldn't find edge to ", - dst->DebugString()); + FormatNodeForError(*dst)); } RemoveEdge(e); AddEdge(new_src, new_src_index, dst, dst_index); diff --git a/tensorflow/core/graph/node_builder.cc b/tensorflow/core/graph/node_builder.cc index 68a20fcc5f..05c6943f2c 100644 --- a/tensorflow/core/graph/node_builder.cc +++ b/tensorflow/core/graph/node_builder.cc @@ -143,7 +143,7 @@ void NodeBuilder::AddIndexError(const Node* node, int i) { errors_.emplace_back(strings::StrCat( "Attempt to add output ", i, " of ", node->name(), " not in range [0, ", node->num_outputs(), ") to node with type ", - def_builder_.op_def().name(), ". Node: ", node->DebugString())); + def_builder_.op_def().name(), ". Node: ", FormatNodeForError(*node))); } } diff --git a/tensorflow/python/client/session.py b/tensorflow/python/client/session.py index c963cfd334..7f783286d3 100644 --- a/tensorflow/python/client/session.py +++ b/tensorflow/python/client/session.py @@ -1283,7 +1283,7 @@ class BaseSession(SessionInterface): # Old format: [[Node: = ...]] # New format: [[{{node }} = ...]] _NODEDEF_NAME_RE = re.compile( - r'\[\[(Node: )?(\{\{node )?([^\} ]*)(\}\})?\s*=') + r'\[\[(Node: )?(\{\{node )?([^\} ]*)(\}\})?\s*=*') def _do_run(self, handle, target_list, fetch_list, feed_dict, options, run_metadata): -- GitLab From 544f7c7ea309c643c59189646edac8a218eb4ae6 Mon Sep 17 00:00:00 2001 From: Peter Hawkins Date: Wed, 17 Oct 2018 17:22:48 -0700 Subject: [PATCH 0241/1825] [TF:XLA] Refactor XlaDevice construction. a) remove the XlaDevice::Create() factory function, instead have users call the constructor directly. Using a factory function makes it difficult to subclass XlaDevice, which is useful to carry along extra backend-specific information as part of the device. b) move the arguments to the constructor into an XlaDevice::Options structure to improve readability and allow backends to only set the relevent subset of the options. No functional changes intended; out-of-tree TF/XLA backends will need to perform a small refactoring (see the changes to xla_cpu_device.cc for an example.) PiperOrigin-RevId: 217618620 --- tensorflow/compiler/jit/BUILD | 4 +- tensorflow/compiler/jit/xla_cpu_device.cc | 24 +++--- tensorflow/compiler/jit/xla_device.cc | 75 +++++++------------ tensorflow/compiler/jit/xla_device.h | 58 +++++++------- tensorflow/compiler/jit/xla_gpu_device.cc | 27 ++++--- .../compiler/jit/xla_interpreter_device.cc | 28 ++++--- tensorflow/compiler/tf2xla/xla_op_registry.h | 1 + 7 files changed, 112 insertions(+), 105 deletions(-) diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD index dddf8e69d9..77cae5668b 100644 --- a/tensorflow/compiler/jit/BUILD +++ b/tensorflow/compiler/jit/BUILD @@ -84,6 +84,7 @@ cc_library( "//tensorflow/compiler/xla/service:cpu_plugin", # buildcleaner: keep "//tensorflow/core:core_cpu_internal", "//tensorflow/core:lib", + "@com_google_absl//absl/memory", ], alwayslink = 1, ) @@ -101,6 +102,7 @@ cc_library( "//tensorflow/compiler/xla/service:gpu_plugin", # buildcleaner: keep "//tensorflow/core:core_cpu_internal", "//tensorflow/core:lib", + "@com_google_absl//absl/memory", ], alwayslink = 1, ) @@ -116,7 +118,7 @@ cc_library( "//tensorflow/compiler/tf2xla:xla_compiler", "//tensorflow/compiler/tf2xla/kernels:xla_ops", "//tensorflow/compiler/xla/service:interpreter_plugin", # buildcleaner: keep - "//tensorflow/core:lib", + "@com_google_absl//absl/memory", ], alwayslink = 1, ) diff --git a/tensorflow/compiler/jit/xla_cpu_device.cc b/tensorflow/compiler/jit/xla_cpu_device.cc index 003c1d8081..f2cea3d000 100644 --- a/tensorflow/compiler/jit/xla_cpu_device.cc +++ b/tensorflow/compiler/jit/xla_cpu_device.cc @@ -16,6 +16,7 @@ limitations under the License. // Registers the XLA_CPU device, which is an XlaDevice instantiation that runs // operators using XLA via the XLA "Host" (CPU) backend. +#include "absl/memory/memory.h" #include "tensorflow/compiler/jit/kernels/xla_ops.h" #include "tensorflow/compiler/jit/legacy_flags/xla_device_flags.h" #include "tensorflow/compiler/jit/xla_compile_on_demand_op.h" @@ -33,7 +34,7 @@ class XlaCpuDeviceFactory : public DeviceFactory { std::vector* devices) override; }; -Status XlaCpuDeviceFactory::CreateDevices(const SessionOptions& options, +Status XlaCpuDeviceFactory::CreateDevices(const SessionOptions& session_options, const string& name_prefix, std::vector* devices) { legacy_flags::XlaDeviceFlags* flags = legacy_flags::GetXlaDeviceFlags(); @@ -44,19 +45,24 @@ Status XlaCpuDeviceFactory::CreateDevices(const SessionOptions& options, registration.requires_compilation = !compile_on_demand; registration.enable_jit_by_default = false; registration.compile_resource_ops = true; + XlaOpRegistry::RegisterCompilationDevice(DEVICE_XLA_CPU, registration); static XlaDeviceOpRegistrations* registrations = RegisterXlaDeviceKernels(DEVICE_XLA_CPU, DEVICE_CPU_XLA_JIT); (void)registrations; - std::unique_ptr device; - TF_RETURN_IF_ERROR(XlaDevice::Create("Host", DEVICE_XLA_CPU, 0, - DEVICE_CPU_XLA_JIT, options, name_prefix, - registration, - /*transfer_as_literal=*/false, - /*use_multiple_streams=*/false, - /*shape_representation_fn=*/{}, - /*padded_shape_fn=*/{}, &device)); + TF_ASSIGN_OR_RETURN(auto platform, + se::MultiPlatformManager::PlatformWithName("Host")); + + XlaDevice::Options options; + options.platform = platform; + options.device_name_prefix = name_prefix; + options.device_name = DEVICE_XLA_CPU; + options.device_ordinal = 0; + options.compilation_device_name = DEVICE_CPU_XLA_JIT; + options.transfer_as_literal = false; + options.use_multiple_streams = false; + auto device = absl::make_unique(session_options, options); devices->push_back(device.release()); return Status::OK(); } diff --git a/tensorflow/compiler/jit/xla_device.cc b/tensorflow/compiler/jit/xla_device.cc index 0824c4644e..17353456eb 100644 --- a/tensorflow/compiler/jit/xla_device.cc +++ b/tensorflow/compiler/jit/xla_device.cc @@ -125,41 +125,17 @@ Status DefaultPaddedShapeFn(const Tensor& tensor, xla::Shape* shape) { return Status::OK(); } -} // namespace - -/* static */ Status XlaDevice::Create( - const string& platform_name, const string& device_name, int device_ordinal, - const string& jit_device_name, const SessionOptions& options, - const string& name_prefix, - const XlaOpRegistry::DeviceRegistration& registration, - bool transfer_as_literal, bool use_multiple_streams, - const XlaCompiler::ShapeRepresentationFn& shape_representation_fn, - const PaddedShapeFn& padded_shape_fn, std::unique_ptr* device) { - VLOG(1) << "XlaDevice::Create " << platform_name << " " << device_name << ":" - << device_ordinal; - - // These are no-ops if they have already been done previously for - // this device_name/compilation_device_name pair. - XlaOpRegistry::RegisterCompilationDevice(device_name, registration); - - auto platform = se::MultiPlatformManager::PlatformWithName(platform_name); - if (!platform.ok()) { - return platform.status(); - } - - const DeviceAttributes attrs = Device::BuildDeviceAttributes( +static DeviceAttributes BuildXlaDeviceAttributes(const string& name_prefix, + const string& device_name, + int device_ordinal) { + return Device::BuildDeviceAttributes( absl::StrCat(name_prefix, "/device:", device_name, ":", device_ordinal), DeviceType(device_name), Bytes(16ULL << 30), DeviceLocality(), absl::StrCat("device: ", device_name, " device")); - - device->reset( - new XlaDevice(options, attrs, device_ordinal, DeviceType(jit_device_name), - platform.ValueOrDie(), transfer_as_literal, - use_multiple_streams, shape_representation_fn, - padded_shape_fn ? padded_shape_fn : DefaultPaddedShapeFn)); - return Status::OK(); } +} // namespace + XlaDevice::Metadata::Metadata( int device_ordinal, se::Platform* platform, const DeviceType& device_type, XlaCompiler::ShapeRepresentationFn shape_representation_fn, @@ -209,24 +185,27 @@ const DeviceType& XlaDevice::Metadata::jit_device_type() const { return GetMetadataFromDevice(ctx->device(), metadata); } -XlaDevice::XlaDevice( - const SessionOptions& options, const DeviceAttributes& attrs, - int device_ordinal, const DeviceType& jit_device_name, - se::Platform* platform, bool transfer_as_literal, bool use_multiple_streams, - const XlaCompiler::ShapeRepresentationFn& shape_representation_fn, - const PaddedShapeFn& padded_shape_fn) - : LocalDevice(options, attrs), - xla_metadata_(device_ordinal, platform, jit_device_name, - shape_representation_fn, padded_shape_fn, - use_multiple_streams), - device_ordinal_(device_ordinal), - jit_device_name_(jit_device_name), - platform_(platform), - use_multiple_streams_(use_multiple_streams), - transfer_as_literal_(transfer_as_literal), - shape_representation_fn_(shape_representation_fn) { - VLOG(1) << "Created XLA device " << jit_device_name << " " << this; - thread_pool_.reset(new thread::ThreadPool(options.env, "xla_device", +XlaDevice::XlaDevice(const SessionOptions& session_options, + const Options& options) + : LocalDevice(session_options, + BuildXlaDeviceAttributes(options.device_name_prefix, + options.device_name, + options.device_ordinal)), + xla_metadata_(options.device_ordinal, options.platform, + DeviceType(options.compilation_device_name), + options.shape_representation_fn, + options.padded_shape_fn ? options.padded_shape_fn + : DefaultPaddedShapeFn, + options.use_multiple_streams), + device_ordinal_(options.device_ordinal), + jit_device_name_(options.compilation_device_name), + platform_(options.platform), + use_multiple_streams_(options.use_multiple_streams), + transfer_as_literal_(options.transfer_as_literal), + shape_representation_fn_(options.shape_representation_fn) { + VLOG(1) << "Created XLA device " << options.compilation_device_name << " " + << this; + thread_pool_.reset(new thread::ThreadPool(session_options.env, "xla_device", /*num_threads=*/1)); } diff --git a/tensorflow/compiler/jit/xla_device.h b/tensorflow/compiler/jit/xla_device.h index 0f06b3fc80..223f0f6649 100644 --- a/tensorflow/compiler/jit/xla_device.h +++ b/tensorflow/compiler/jit/xla_device.h @@ -92,34 +92,40 @@ class XlaDevice : public LocalDevice { static Status GetMetadata(OpKernelConstruction* ctx, const Metadata** metadata); - // Factory function. 'platform_name' is the name of the XLA platform. - // 'device_name' is the name of the Tensorflow device to create. - // 'jit_device_name' is the name of the corresponding JIT device. - // 'transfer_as_literal' is true if device<->host transfers must be done using - // XLA's TransferLiteral{To,From}Device interface. If false, we can use - // ThenMemcpy instead. - // If 'use_multiple_streams' is true, we create separate streams for - // host-to-device and device-to-host communication. - // If padded_shape_fn is empty, a default implementation that returns - // the on-host shape is used. - static Status Create( - const string& platform_name, const string& device_name, - int device_ordinal, const string& jit_device_name, - const SessionOptions& options, const string& name_prefix, - const XlaOpRegistry::DeviceRegistration& registration, - bool transfer_as_literal, bool use_multiple_streams, - const XlaCompiler::ShapeRepresentationFn& shape_representation_fn, - const PaddedShapeFn& padded_shape_fn, std::unique_ptr* device); + struct Options { + // The StreamExecutor platform. Not owned. Must be non-null. + se::Platform* platform = nullptr; + + // The device name's prefix (e.g., "/task:7") + string device_name_prefix; + + // The name of the XLA device (e.g., "XLA_CPU") + string device_name; + + // The number of the device. + int device_ordinal = -1; + + // The name of the compilation device (e.g., "XLA_CPU_JIT"); + string compilation_device_name; + + // 'transfer_as_literal' is true if device<->host transfers must be done + // using XLA's TransferLiteral{To,From}Device interface. If false, we can + // use ThenMemcpy instead. + bool transfer_as_literal = false; + + // If 'use_multiple_streams' is true, we create separate streams for + // compute, host-to-device, and device-to-host communication. + bool use_multiple_streams = false; + + XlaCompiler::ShapeRepresentationFn shape_representation_fn; + + // If padded_shape_fn is empty, a default implementation that returns + // the logical on-device shape without padding is used. + PaddedShapeFn padded_shape_fn; + }; // Creates a new XLA Device. - // If padded_shape_fn is empty, a default implementation that returns - // the logical on-device shape without padding is used. - XlaDevice(const SessionOptions& options, const DeviceAttributes& attrs, - int device_ordinal, const DeviceType& jit_device_name, - se::Platform* platform, bool transfer_as_literal, - bool use_multiple_streams, - const XlaCompiler::ShapeRepresentationFn& shape_representation_fn, - const PaddedShapeFn& padded_shape_fn); + XlaDevice(const SessionOptions& session_options, const Options& options); ~XlaDevice() override; Allocator* GetAllocator(AllocatorAttributes attr) override diff --git a/tensorflow/compiler/jit/xla_gpu_device.cc b/tensorflow/compiler/jit/xla_gpu_device.cc index 60979556a3..d9021fb001 100644 --- a/tensorflow/compiler/jit/xla_gpu_device.cc +++ b/tensorflow/compiler/jit/xla_gpu_device.cc @@ -16,6 +16,7 @@ limitations under the License. // Registers the XLA_GPU device, which is an XlaDevice instantiation that runs // operators using XLA via the XLA "CUDA" (GPU) backend. +#include "absl/memory/memory.h" #include "tensorflow/compiler/jit/kernels/xla_ops.h" #include "tensorflow/compiler/jit/xla_device.h" #include "tensorflow/compiler/jit/xla_device_ops.h" @@ -31,7 +32,7 @@ class XlaGpuDeviceFactory : public DeviceFactory { std::vector* devices) override; }; -Status XlaGpuDeviceFactory::CreateDevices(const SessionOptions& options, +Status XlaGpuDeviceFactory::CreateDevices(const SessionOptions& session_options, const string& name_prefix, std::vector* devices) { XlaOpRegistry::DeviceRegistration registration; @@ -39,25 +40,29 @@ Status XlaGpuDeviceFactory::CreateDevices(const SessionOptions& options, registration.requires_compilation = true; registration.enable_jit_by_default = false; registration.compile_resource_ops = true; + XlaOpRegistry::RegisterCompilationDevice(DEVICE_XLA_GPU, registration); static XlaDeviceOpRegistrations* registrations = RegisterXlaDeviceKernels(DEVICE_XLA_GPU, DEVICE_GPU_XLA_JIT); (void)registrations; - std::unique_ptr device; - Status status = - XlaDevice::Create("CUDA", DEVICE_XLA_GPU, 0, DEVICE_GPU_XLA_JIT, options, - name_prefix, registration, - /*transfer_as_literal=*/false, - /*use_multiple_streams=*/false, - /*shape_representation_fn=*/{}, - /*padded_shape_fn=*/{}, &device); - if (!status.ok()) { + auto platform = se::MultiPlatformManager::PlatformWithName("CUDA"); + if (!platform.ok()) { // Treat failures as non-fatal; there might not be a GPU in the machine. - VLOG(1) << "Failed to create XLA_GPU device: " << status; + VLOG(1) << "Failed to create XLA_GPU device: " << platform.status(); return Status::OK(); } + XlaDevice::Options options; + options.platform = platform.ValueOrDie(); + options.device_name_prefix = name_prefix; + options.device_name = DEVICE_XLA_GPU; + options.device_ordinal = 0; + options.compilation_device_name = DEVICE_GPU_XLA_JIT; + options.transfer_as_literal = false; + options.use_multiple_streams = false; + auto device = absl::make_unique(session_options, options); + // TODO(b/78468222): Uncomment after fixing this bug // status = device->UseGpuDeviceInfo(); // if (!status.ok()) { diff --git a/tensorflow/compiler/jit/xla_interpreter_device.cc b/tensorflow/compiler/jit/xla_interpreter_device.cc index 8a80639b63..aee3b58c99 100644 --- a/tensorflow/compiler/jit/xla_interpreter_device.cc +++ b/tensorflow/compiler/jit/xla_interpreter_device.cc @@ -15,6 +15,7 @@ limitations under the License. // Registers the XLA_INTERPRETER device which exposes the XLA Interpreter. +#include "absl/memory/memory.h" #include "tensorflow/compiler/jit/kernels/xla_ops.h" #include "tensorflow/compiler/jit/xla_device.h" #include "tensorflow/compiler/jit/xla_device_ops.h" @@ -36,7 +37,7 @@ class XlaInterpreterDeviceFactory : public DeviceFactory { }; Status XlaInterpreterDeviceFactory::CreateDevices( - const SessionOptions& options, const string& name_prefix, + const SessionOptions& session_options, const string& name_prefix, std::vector* devices) { static XlaDeviceOpRegistrations* registrations = RegisterXlaDeviceKernels( DEVICE_XLA_INTERPRETER, DEVICE_INTERPRETER_XLA_JIT); @@ -47,16 +48,23 @@ Status XlaInterpreterDeviceFactory::CreateDevices( registration.requires_compilation = true; registration.enable_jit_by_default = false; registration.compile_resource_ops = true; - - std::unique_ptr device; - TF_RETURN_IF_ERROR(XlaDevice::Create("Interpreter", DEVICE_XLA_INTERPRETER, 0, - DEVICE_INTERPRETER_XLA_JIT, options, - name_prefix, registration, - /*transfer_as_literal=*/false, - /*use_multiple_streams=*/false, - /*shape_representation_fn=*/{}, - /*padded_shape_fn=*/{}, &device)); + XlaOpRegistry::RegisterCompilationDevice(DEVICE_XLA_INTERPRETER, + registration); + + TF_ASSIGN_OR_RETURN( + auto platform, se::MultiPlatformManager::PlatformWithName("Interpreter")); + + XlaDevice::Options options; + options.platform = platform; + options.device_name_prefix = name_prefix; + options.device_name = DEVICE_XLA_INTERPRETER; + options.device_ordinal = 0; + options.compilation_device_name = DEVICE_INTERPRETER_XLA_JIT; + options.transfer_as_literal = false; + options.use_multiple_streams = false; + auto device = absl::make_unique(session_options, options); devices->push_back(device.release()); + return Status::OK(); } diff --git a/tensorflow/compiler/tf2xla/xla_op_registry.h b/tensorflow/compiler/tf2xla/xla_op_registry.h index 4b2c2bacd6..413e1eeaff 100644 --- a/tensorflow/compiler/tf2xla/xla_op_registry.h +++ b/tensorflow/compiler/tf2xla/xla_op_registry.h @@ -106,6 +106,7 @@ class XlaOpRegistry { // Registers `device_name` for XLA compilation, using information from // `registration`. + // Does nothing if a registration for `device_name` already exists. static void RegisterCompilationDevice(const string& device_name, const DeviceRegistration& registration); -- GitLab From 15e65b922a76a69e3734cee8e74d2c9b649e1b8d Mon Sep 17 00:00:00 2001 From: James Qin Date: Wed, 17 Oct 2018 17:32:02 -0700 Subject: [PATCH 0242/1825] Fix cudnn breakage PiperOrigin-RevId: 217619690 --- tensorflow/contrib/cudnn_rnn/BUILD | 9 +--- .../python/kernel_tests/cudnn_rnn_test.py | 20 +++++--- .../cudnn_rnn/python/layers/cudnn_rnn.py | 3 +- tensorflow/core/kernels/cudnn_rnn_ops.cc | 14 ++++- tensorflow/stream_executor/cuda/cuda_dnn.cc | 23 ++++++--- tensorflow/stream_executor/stream.cc | 51 ++++++++++++------- 6 files changed, 76 insertions(+), 44 deletions(-) diff --git a/tensorflow/contrib/cudnn_rnn/BUILD b/tensorflow/contrib/cudnn_rnn/BUILD index aeefa3cee6..8d9ff15868 100644 --- a/tensorflow/contrib/cudnn_rnn/BUILD +++ b/tensorflow/contrib/cudnn_rnn/BUILD @@ -9,8 +9,6 @@ licenses(["notice"]) # Apache 2.0 exports_files(["LICENSE"]) -load("//tensorflow:tensorflow.bzl", "tf_gen_op_libs") -load("//tensorflow:tensorflow.bzl", "tf_gen_op_wrapper_py") load("//tensorflow:tensorflow.bzl", "cuda_py_test") load("//tensorflow:tensorflow.bzl", "tf_custom_op_py_library") @@ -65,8 +63,7 @@ cuda_py_test( ], shard_count = 6, tags = [ - "manual", - "requires_cudnn5", + "noasan", # http://b/62067814 ], ) @@ -93,8 +90,7 @@ cuda_py_test( ], shard_count = 6, tags = [ - "manual", - "requires_cudnn5", + "noasan", # http://b/62067814 ], ) @@ -121,6 +117,5 @@ cuda_py_test( "noasan", # http://b/62067814 "nomsan", "notsan", - "requires_cudnn5", ], ) 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 57793a8ff5..d3a1b33233 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 @@ -1184,7 +1184,8 @@ class CudnnRNNTestTraining(test_util.TensorFlowTestCase): num_grads = [self._ComputeNumericGrad(sess, y, x, delta) for x in xs] self.assertEqual(len(sym_grads), len(num_grads)) - for sym, num in zip(sym_grads, num_grads): + for x, sym, num in zip(xs, sym_grads, num_grads): + logging.info("Comparing gradients for input: %s", x.name) self.assertFalse(np.any(np.isnan(sym))) self.assertFalse(np.any(np.isnan(num))) self.assertAllClose(sym, num, atol=tolerance, rtol=tolerance) @@ -1225,18 +1226,18 @@ class CudnnRNNTestTraining(test_util.TensorFlowTestCase): params = rnn.trainable_variables[0] inputs = variables.Variable( - random_ops.random_uniform( - [seq_length, batch_size, input_size], dtype=dtype), - dtype=dtype) + random_ops.random_uniform([seq_length, batch_size, input_size], + dtype=dtype), + dtype=dtype).read_value() input_h = variables.Variable( random_ops.random_uniform( [num_layers * dir_count, batch_size, num_units], dtype=dtype), - dtype=dtype) + dtype=dtype).read_value() if has_input_c: input_c = variables.Variable( random_ops.random_uniform( [num_layers * dir_count, batch_size, num_units], dtype=dtype), - dtype=dtype) + dtype=dtype).read_value() initial_state = (input_h, input_c) else: initial_state = (input_h,) @@ -1262,7 +1263,7 @@ class CudnnRNNTestTraining(test_util.TensorFlowTestCase): def _TestSimpleTrainingHelper(self, rnn_mode, test_configs): dropouts = [0, 0.5, 1.] - v2_options = [str(False), str(True)] + v2_options = [False, True] for config, dropout, use_v2 in itertools.product(test_configs, dropouts, v2_options): dtype = config.get("dtype", dtypes.float32) @@ -1270,6 +1271,9 @@ class CudnnRNNTestTraining(test_util.TensorFlowTestCase): tolerance = config.get("tolerance", 1e-6) dir_count = config.get("dir_count", 1) shape = config["shape"] + if dtype == dtypes.float64: + # TODO(jamesqin): b/117848763 + use_v2 = False with ops.Graph().as_default(): self._TestOneSimpleTraining( rnn_mode, shape["num_layers"], shape["num_units"], @@ -1519,7 +1523,7 @@ if __name__ == "__main__": parser.add_argument( "--grad_check_num_samples", type=int, - default=5, + default=1, help="Number of samples to run for gradient check.") FLAGS, unparsed = parser.parse_known_args() sys.argv = [argv0] + unparsed diff --git a/tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py b/tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py index e26d56c857..a324c6e7d7 100644 --- a/tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py +++ b/tensorflow/contrib/cudnn_rnn/python/layers/cudnn_rnn.py @@ -356,7 +356,8 @@ class _CudnnRNN(base_layer.Layer): "Partitioner is not supported for Cudnn RNN layer variables, using " "it will create forward-compatibility issues with future " "CUDA/CuDNN generations.") - # Initialize opaque params with a tensor. + # Initialize opaque params with a tensor with unknown shape, thus couldn't + # use self.add_variable(name, shape, initializer, ...) self.kernel = vs.get_variable( "opaque_kernel", dtype=self._plain_dtype, initializer=opaque_params_t, validate_shape=False) diff --git a/tensorflow/core/kernels/cudnn_rnn_ops.cc b/tensorflow/core/kernels/cudnn_rnn_ops.cc index 04959df38d..dfcc302f46 100644 --- a/tensorflow/core/kernels/cudnn_rnn_ops.cc +++ b/tensorflow/core/kernels/cudnn_rnn_ops.cc @@ -1357,6 +1357,10 @@ class CudnnRNNForwardOpV2 modeltypes.rnn_mode, modeltypes.rnn_input_mode, input->dtype()); if (AutoTuneRnnConfigMap::GetInstance()->Find(rnn_params, algo_config)) { + VLOG(1) << "Using existing best Cudnn RNN algorithm " + << "(algo, tensor_op_enabled) = (" + << algo_config->algorithm().algo_id() << ", " + << algo_config->algorithm().tensor_ops_enabled() << ")."; return Status::OK(); } @@ -1390,6 +1394,8 @@ class CudnnRNNForwardOpV2 } ProfileResult best_result; for (auto& algo : algorithms) { + VLOG(1) << "Profile Cudnn RNN algorithm (algo, tensor_op_enabled) = (" + << algo.algo_id() << ", " << algo.tensor_ops_enabled() << ")."; Status status; ProfileResult final_profile_result; @@ -1438,8 +1444,9 @@ class CudnnRNNForwardOpV2 } auto total_time = final_profile_result.elapsed_time_in_ms(); - VLOG(1) << "Profile Cudnn RNN algo " << algo.algo_id() - << " run time: " << total_time << " ms"; + VLOG(1) << "Cudnn RNN algorithm (algo, tensor_op_enabled) = (" + << algo.algo_id() << ", " << algo.tensor_ops_enabled() << ")" + << " run time: " << total_time << " ms."; if (total_time < best_result.elapsed_time_in_ms()) { best_result.set_elapsed_time_in_ms(total_time); best_result.set_algorithm(algo); @@ -1450,6 +1457,9 @@ class CudnnRNNForwardOpV2 return Status(error::Code::INTERNAL, "No algorithm worked!"); } algo_config->set_algorithm(best_result.algorithm()); + VLOG(1) << "Best Cudnn RNN algorithm (algo, tensor_op_enabled) = (" + << best_result.algorithm().algo_id() << ", " + << best_result.algorithm().tensor_ops_enabled() << ")."; AutoTuneRnnConfigMap::GetInstance()->Insert(rnn_params, *algo_config); return Status::OK(); } diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc index df8538a4b8..4765aa1a7b 100644 --- a/tensorflow/stream_executor/cuda/cuda_dnn.cc +++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc @@ -505,13 +505,13 @@ RnnDescriptor CreateRnnDescriptor() { CHECK_CUDNN_OK(cudnnCreateRNNDescriptor(&result)); return RnnDescriptor(result); } -PersistentRnnPlan CreatePersistentRnnPlan(cudnnRNNDescriptor_t rnn_desc, - int batch_size, - cudnnDataType_t data_type) { + +port::StatusOr CreatePersistentRnnPlan( + cudnnRNNDescriptor_t rnn_desc, int batch_size, cudnnDataType_t data_type) { cudnnPersistentRNNPlan_t result; - CHECK_CUDNN_OK( + RETURN_IF_CUDNN_ERROR( cudnnCreatePersistentRNNPlan(rnn_desc, batch_size, data_type, &result)); - return PersistentRnnPlan(result); + return port::StatusOr(PersistentRnnPlan(result)); } // Turns a BatchDescriptor structure into a cudnn tensor handle within a @@ -1042,12 +1042,19 @@ class CudnnRnnDescriptor : public dnn::RnnDescriptor { /*mode=*/rnn_mode, /*algo=*/rnn_algo, /*dataType=*/compute_type)); + port::StatusOr rnn_plan_wrapper; PersistentRnnPlan rnn_plan; if (rnn_algo == CUDNN_RNN_ALGO_PERSIST_DYNAMIC) { CHECK_GE(batch_size, 0); - rnn_plan = CreatePersistentRnnPlan(rnn_desc.get(), batch_size, data_type); - RETURN_IF_CUDNN_ERROR( - cudnnSetPersistentRNNPlan(rnn_desc.get(), rnn_plan.get())); + rnn_plan_wrapper = + CreatePersistentRnnPlan(rnn_desc.get(), batch_size, data_type); + if (!rnn_plan_wrapper.ok()) { + return port::StatusOr(rnn_plan_wrapper.status()); + } else { + rnn_plan = rnn_plan_wrapper.ConsumeValueOrDie(); + RETURN_IF_CUDNN_ERROR( + cudnnSetPersistentRNNPlan(rnn_desc.get(), rnn_plan.get())); + } } // Create the params handle. diff --git a/tensorflow/stream_executor/stream.cc b/tensorflow/stream_executor/stream.cc index 69558fd14b..5ce27a49c5 100644 --- a/tensorflow/stream_executor/stream.cc +++ b/tensorflow/stream_executor/stream.cc @@ -5087,15 +5087,17 @@ Stream &Stream::ThenRnnForward( // TODO(zhengxq): add VLOG PARAM calls. if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoRnnForward( + auto status = dnn->DoRnnForward( this, rnn_desc, input_desc, input_data, input_h_desc, input_h_data, input_c_desc, input_c_data, params, output_desc, output_data, output_h_desc, output_h_data, output_c_desc, output_c_data, is_training, reserve_space_allocator, workspace_allocator, - output_profile_result)); + output_profile_result); + if (!status && !output_profile_result) { + SetError(); + } } else { - SetError(); - LOG(WARNING) << "Attempting to call ThenRnnForward without DNN support"; + SetErrorAndLogNoDnnSupport(); } } return *this; @@ -5121,15 +5123,17 @@ Stream &Stream::ThenRnnForward( // TODO(zhengxq): add VLOG PARAM calls. if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoRnnForward( + auto status = dnn->DoRnnForward( this, rnn_desc, input_desc, input_data, input_h_desc, input_h_data, input_c_desc, input_c_data, params, output_desc, output_data, output_h_desc, output_h_data, output_c_desc, output_c_data, is_training, reserve_space_allocator, workspace_allocator, - output_profile_result)); + output_profile_result); + if (!status && !output_profile_result) { + SetError(); + } } else { - SetError(); - LOG(WARNING) << "Attempting to call ThenRnnForward without DNN support"; + SetErrorAndLogNoDnnSupport(); } } return *this; @@ -5156,15 +5160,17 @@ Stream &Stream::ThenRnnForward( // TODO(zhengxq): add VLOG PARAM calls. if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoRnnForward( + auto status = dnn->DoRnnForward( this, rnn_desc, input_desc, input_data, input_h_desc, input_h_data, input_c_desc, input_c_data, params, output_desc, output_data, output_h_desc, output_h_data, output_c_desc, output_c_data, is_training, reserve_space_allocator, workspace_allocator, - output_profile_result)); + output_profile_result); + if (!status && !output_profile_result) { + SetError(); + } } else { - SetError(); - LOG(WARNING) << "Attempting to call ThenRnnForward without DNN support"; + SetErrorAndLogNoDnnSupport(); } } return *this; @@ -5198,14 +5204,17 @@ Stream &Stream::ThenRnnBackward( // TODO(zhengxq): add VLOG PARAM calls. if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoRnnBackward( + auto status = dnn->DoRnnBackward( this, rnn_desc, input_desc, input_data, input_h_desc, input_h_data, input_c_desc, input_c_data, params, output_desc, output_data, output_h_desc, output_h_data, output_c_desc, output_c_data, output_backprop_data, output_h_backprop_data, output_c_backprop_data, input_backprop_data, input_h_backprop_data, input_c_backprop_data, params_backprop_data, reserve_space_data, workspace_allocator, - output_profile_result)); + output_profile_result); + if (!status && !output_profile_result) { + SetError(); + } } else { SetError(); LOG(WARNING) << "Attempting to call ThenRnnBackward without DNN support"; @@ -5241,14 +5250,17 @@ Stream &Stream::ThenRnnBackward( // TODO(zhengxq): add VLOG PARAM calls. if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoRnnBackward( + auto status = dnn->DoRnnBackward( this, rnn_desc, input_desc, input_data, input_h_desc, input_h_data, input_c_desc, input_c_data, params, output_desc, output_data, output_h_desc, output_h_data, output_c_desc, output_c_data, output_backprop_data, output_h_backprop_data, output_c_backprop_data, input_backprop_data, input_h_backprop_data, input_c_backprop_data, params_backprop_data, reserve_space_data, workspace_allocator, - output_profile_result)); + output_profile_result); + if (!status && !output_profile_result) { + SetError(); + } } else { SetError(); LOG(WARNING) << "Attempting to call ThenRnnBackward without DNN support"; @@ -5285,14 +5297,17 @@ Stream &Stream::ThenRnnBackward( // TODO(zhengxq): add VLOG PARAM calls. if (ok()) { if (dnn::DnnSupport *dnn = parent_->AsDnn()) { - CheckError(dnn->DoRnnBackward( + auto status = dnn->DoRnnBackward( this, rnn_desc, input_desc, input_data, input_h_desc, input_h_data, input_c_desc, input_c_data, params, output_desc, output_data, output_h_desc, output_h_data, output_c_desc, output_c_data, output_backprop_data, output_h_backprop_data, output_c_backprop_data, input_backprop_data, input_h_backprop_data, input_c_backprop_data, params_backprop_data, reserve_space_data, workspace_allocator, - output_profile_result)); + output_profile_result); + if (!status && !output_profile_result) { + SetError(); + } } else { SetError(); LOG(WARNING) << "Attempting to call ThenRnnBackward without DNN support"; -- GitLab From 440113ee2a95aabaf4868ac8dce2613bace4d158 Mon Sep 17 00:00:00 2001 From: Anjali Sridhar Date: Wed, 17 Oct 2018 17:39:49 -0700 Subject: [PATCH 0243/1825] Add the `num_replicas_per_sync` API to return the number of replicas that are coordinating gradient updates. PiperOrigin-RevId: 217620678 --- .../python/collective_all_reduce_strategy.py | 5 +++++ .../python/collective_all_reduce_strategy_test.py | 10 ++++++++++ .../contrib/distribute/python/mirrored_strategy.py | 6 +++++- .../python/mirrored_strategy_multigpu_test.py | 13 ++++++++++++- .../distribute/python/parameter_server_strategy.py | 4 ++++ .../python/parameter_server_strategy_test.py | 7 +++++++ .../contrib/distribute/python/tpu_strategy.py | 5 ++++- tensorflow/python/training/distribute.py | 5 +++++ 8 files changed, 52 insertions(+), 3 deletions(-) diff --git a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py index 9809204f8f..744cb7572e 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy.py @@ -278,3 +278,8 @@ class CollectiveAllReduceStrategy(mirrored_strategy.MirroredStrategy): @property def should_save_summary(self): return self._is_chief + + @property + def num_replicas_in_sync(self): + return len(self._devices) * self._num_workers + 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 6796a23d46..de2c03f1a4 100644 --- a/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py +++ b/tensorflow/contrib/distribute/python/collective_all_reduce_strategy_test.py @@ -247,6 +247,16 @@ class DistributedCollectiveAllReduceStrategyTest( cls._cluster_spec = multi_worker_test_base.create_in_process_cluster( num_workers=3, num_ps=0) + def test_num_replicas_in_sync(self): + distribution = collective_all_reduce_strategy.CollectiveAllReduceStrategy( + num_gpus_per_worker=2) + distribution.configure(cluster_spec=self._cluster_spec, task_type='worker', + task_id=0) + num_workers = len(self._cluster_spec.get('chief', []) + + self._cluster_spec.get('worker', [])) + self.assertEqual(2 * num_workers, + distribution.num_replicas_in_sync) + @combinations.generate( combinations.combine(mode=['graph'], num_gpus=[0, 1, 2], required_gpus=1)) def testMinimizeLossGraph(self, num_gpus): diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy.py b/tensorflow/contrib/distribute/python/mirrored_strategy.py index 0f82508428..2b128935a4 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy.py @@ -365,7 +365,7 @@ class MirroredStrategy(distribute_lib.DistributionStrategy): self._cross_tower_ops = cross_tower_ops self._prefetch_on_device = prefetch_on_device self._auto_shard_dataset = auto_shard_dataset - # Rememeber num GPUs which might be needed by `configure` method. + # Remember num GPUs which might be needed by `configure` method. if num_gpus is not None and num_gpus_per_worker is not None: raise ValueError( "You cannot specify both `num_gpus` and `num_gpus_per_worker`.") @@ -687,6 +687,10 @@ class MirroredStrategy(distribute_lib.DistributionStrategy): def num_towers(self): return len(self._devices) + @property + def num_replicas_in_sync(self): + return len(self._devices) + def _worker_device_index(self): return self._device_index diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py index fd833c772d..d32c04068c 100644 --- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py +++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py @@ -98,6 +98,12 @@ class MirroredTwoDeviceDistributionTest(strategy_test_lib.DistributionTestBase): self.skipTest("Not GPU test") self.assertEqual(2, self._get_distribution_strategy().num_towers) + def testNumReplicasInSync(self): + if not GPU_TEST: + self.skipTest("Not GPU test") + self.assertEqual(2, self._get_distribution_strategy(). + num_replicas_in_sync) + @test_util.run_in_graph_and_eager_modes def testCallAndMergeExceptions(self): if not GPU_TEST: @@ -1429,7 +1435,6 @@ class MirroredStrategyDefunTest(test.TestCase): self.assertAllEqual([0.5], updated_var_values[1]) - class MultiWorkerMirroredStrategyTest( multi_worker_test_base.MultiWorkerTestBase, strategy_test_lib.DistributionTestBase): @@ -1442,6 +1447,12 @@ class MultiWorkerMirroredStrategyTest( strategy.configure(cluster_spec=cluster_spec) return strategy + def test_num_replicas_in_sync(self): + strategy = self._get_distribution_strategy() + # We calculate the total number of gpus across the workers(2) specified in + # the cluster spec. + self.assertEqual(context.num_gpus() * 2, strategy.num_replicas_in_sync) + def testMinimizeLossGraph(self): self._test_minimize_loss_graph(self._get_distribution_strategy(), learning_rate=0.05) diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy.py b/tensorflow/contrib/distribute/python/parameter_server_strategy.py index 6ddd91507b..03b8564996 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy.py @@ -441,6 +441,10 @@ class ParameterServerStrategy(distribute_lib.DistributionStrategy): def num_towers(self): return len(self._compute_devices) + @property + def num_replicas_in_sync(self): + return len(self._compute_devices) + @property def worker_devices(self): # Make a copy to prevent users from accidentally mutating our copy. diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py index 9c112e4f85..91f1fae47e 100644 --- a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py +++ b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py @@ -440,6 +440,13 @@ 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) + # 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) + def testDeviceAssignmentLocalCPU(self): distribution = parameter_server_strategy.ParameterServerStrategy( num_gpus_per_worker=0) diff --git a/tensorflow/contrib/distribute/python/tpu_strategy.py b/tensorflow/contrib/distribute/python/tpu_strategy.py index 1d9e299b38..e4bd7428c2 100644 --- a/tensorflow/contrib/distribute/python/tpu_strategy.py +++ b/tensorflow/contrib/distribute/python/tpu_strategy.py @@ -441,7 +441,6 @@ class TPUStrategy(one_device_strategy.OneDeviceStrategy): return val return [val] - @property def num_towers(self): return self._num_cores_override or self._tpu_metadata.num_cores @@ -454,6 +453,10 @@ class TPUStrategy(one_device_strategy.OneDeviceStrategy): def num_towers_per_host(self): return self._tpu_metadata.num_of_cores_per_host + @property + def num_replicas_in_sync(self): + return self.num_towers + @property def between_graph(self): return False diff --git a/tensorflow/python/training/distribute.py b/tensorflow/python/training/distribute.py index 144b167170..4539a7d4e4 100644 --- a/tensorflow/python/training/distribute.py +++ b/tensorflow/python/training/distribute.py @@ -910,6 +910,11 @@ class DistributionStrategy(object): """Returns number of towers, for purposes of averaging across towers.""" raise NotImplementedError("must be implemented in descendants") + @property + def num_replicas_in_sync(self): + """Returns number of replicas over which gradients are aggregated.""" + raise NotImplementedError("must be implemented in descendants") + @property def worker_devices(self): """Returns the list of devices used to run `call_for_each_tower()` calls.""" -- GitLab From 7e0257d953401288bc10dc11d07b418371bbc56d Mon Sep 17 00:00:00 2001 From: Pete Warden Date: Wed, 17 Oct 2018 17:53:04 -0700 Subject: [PATCH 0244/1825] Fix alignment for TensorFlow Lite's Microcontroller interpreter PiperOrigin-RevId: 217622054 --- .../experimental/micro/micro_interpreter.cc | 10 ++--- .../micro/simple_tensor_allocator.cc | 43 ++++++++++++------- .../micro/simple_tensor_allocator.h | 2 +- .../micro/simple_tensor_allocator_test.cc | 29 ++++++++++++- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/tensorflow/contrib/lite/experimental/micro/micro_interpreter.cc b/tensorflow/contrib/lite/experimental/micro/micro_interpreter.cc index 0f38991bb0..5ece5edc31 100644 --- a/tensorflow/contrib/lite/experimental/micro/micro_interpreter.cc +++ b/tensorflow/contrib/lite/experimental/micro/micro_interpreter.cc @@ -82,7 +82,7 @@ MicroInterpreter::MicroInterpreter(const Model* model, context_.tensors_size = tensors_->Length(); context_.tensors = reinterpret_cast(tensor_allocator_->AllocateMemory( - sizeof(TfLiteTensor) * context_.tensors_size)); + sizeof(TfLiteTensor) * context_.tensors_size, 4)); for (int i = 0; i < subgraph_->inputs()->Length(); ++i) { const int tensor_index = subgraph_->inputs()->Get(i); const auto* tensor = tensors_->Get(tensor_index); @@ -94,10 +94,10 @@ MicroInterpreter::MicroInterpreter(const Model* model, } } - int* first_created = reinterpret_cast( - tensor_allocator_->AllocateMemory(sizeof(int) * tensors_->Length())); - int* last_used = reinterpret_cast( - tensor_allocator_->AllocateMemory(sizeof(int) * tensors_->Length())); + int* first_created = reinterpret_cast(tensor_allocator_->AllocateMemory( + sizeof(int) * tensors_->Length(), sizeof(int))); + int* last_used = reinterpret_cast(tensor_allocator_->AllocateMemory( + sizeof(int) * tensors_->Length(), sizeof(int))); for (int i = 0; i < tensors_->Length(); ++i) { first_created[i] = -1; last_used[i] = -1; diff --git a/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator.cc b/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator.cc index 8c090a20a5..555e53afef 100644 --- a/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator.cc +++ b/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator.cc @@ -54,18 +54,24 @@ TfLiteStatus TfLiteTypeSizeOf(TfLiteType type, size_t* size, } TfLiteStatus BytesRequired(const tflite::Tensor& flatbuffer_tensor, - size_t dims_size, size_t* bytes, + size_t dims_size, size_t* bytes, size_t* type_size, ErrorReporter* error_reporter) { TfLiteType tf_lite_type; TF_LITE_ENSURE_STATUS(ConvertTensorType(flatbuffer_tensor.type(), &tf_lite_type, error_reporter)); - size_t type_size; TF_LITE_ENSURE_STATUS( - TfLiteTypeSizeOf(tf_lite_type, &type_size, error_reporter)); - *bytes = dims_size * type_size; + TfLiteTypeSizeOf(tf_lite_type, type_size, error_reporter)); + *bytes = dims_size * (*type_size); return kTfLiteOk; } +uint8_t* AlignPointerRoundUp(uint8_t* data, size_t alignment) { + size_t data_as_size_t = reinterpret_cast(data); + uint8_t* aligned_result = reinterpret_cast( + ((data_as_size_t + (alignment - 1)) / alignment) * alignment); + return aligned_result; +} + } // namespace TfLiteStatus SimpleTensorAllocator::AllocateTensor( @@ -84,8 +90,10 @@ TfLiteStatus SimpleTensorAllocator::AllocateTensor( if (size_t array_size = array->size()) { result->data.raw = const_cast(reinterpret_cast(array->data())); + size_t type_size; TF_LITE_ENSURE_STATUS(BytesRequired(flatbuffer_tensor, array_size, - &result->bytes, error_reporter)); + &result->bytes, &type_size, + error_reporter)); } } } @@ -96,9 +104,12 @@ TfLiteStatus SimpleTensorAllocator::AllocateTensor( for (int n = 0; n < flatbuffer_tensor.shape()->Length(); ++n) { data_size *= flatbuffer_tensor.shape()->Get(n); } + size_t type_size; TF_LITE_ENSURE_STATUS(BytesRequired(flatbuffer_tensor, data_size, - &result->bytes, error_reporter)); - result->data.raw = reinterpret_cast(AllocateMemory(result->bytes)); + &result->bytes, &type_size, + error_reporter)); + result->data.raw = + reinterpret_cast(AllocateMemory(result->bytes, type_size)); if (result->data.raw == nullptr) { const char* tensor_name = flatbuffer_tensor.name()->c_str(); if (tensor_name == nullptr) { @@ -112,8 +123,8 @@ TfLiteStatus SimpleTensorAllocator::AllocateTensor( } result->allocation_type = kTfLiteArenaRw; } - result->dims = reinterpret_cast( - AllocateMemory(sizeof(int) * (flatbuffer_tensor.shape()->Length() + 1))); + result->dims = reinterpret_cast(AllocateMemory( + sizeof(int) * (flatbuffer_tensor.shape()->Length() + 1), sizeof(int))); result->dims->size = flatbuffer_tensor.shape()->Length(); for (int n = 0; n < flatbuffer_tensor.shape()->Length(); ++n) { result->dims->data[n] = flatbuffer_tensor.shape()->Get(n); @@ -135,15 +146,17 @@ TfLiteStatus SimpleTensorAllocator::AllocateTensor( return kTfLiteOk; } -uint8_t* SimpleTensorAllocator::AllocateMemory(size_t size) { - if ((data_size_ + size) > data_size_max_) { +uint8_t* SimpleTensorAllocator::AllocateMemory(size_t size, size_t alignment) { + uint8_t* current_data = data_ + data_size_; + uint8_t* aligned_result = AlignPointerRoundUp(current_data, alignment); + uint8_t* next_free = aligned_result + size; + size_t aligned_size = (next_free - current_data); + if ((data_size_ + aligned_size) > data_size_max_) { // TODO(petewarden): Add error reporting beyond returning null! return nullptr; } - uint8_t* result = data_; - data_ += size; - data_size_ += size; - return result; + data_size_ += aligned_size; + return aligned_result; } } // namespace tflite diff --git a/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator.h b/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator.h index 4f16a9d0e5..56fb293675 100644 --- a/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator.h +++ b/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator.h @@ -36,7 +36,7 @@ class SimpleTensorAllocator { const flatbuffers::Vector>* buffers, ErrorReporter* error_reporter, TfLiteTensor* result); - uint8_t* AllocateMemory(size_t size); + uint8_t* AllocateMemory(size_t size, size_t alignment); int GetDataSize() const { return data_size_; } diff --git a/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator_test.cc b/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator_test.cc index c835427243..ab19394502 100644 --- a/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator_test.cc +++ b/tensorflow/contrib/lite/experimental/micro/simple_tensor_allocator_test.cc @@ -121,7 +121,7 @@ TF_LITE_MICRO_TEST(TestTooLarge) { uint8_t arena[arena_size]; tflite::SimpleTensorAllocator allocator(arena, arena_size); - const tflite::Tensor* tensor = tflite::Create1dTensor(10000); + const tflite::Tensor* tensor = tflite::Create1dTensor(2000); const flatbuffers::Vector>* buffers = tflite::CreateBuffers(); @@ -137,8 +137,33 @@ TF_LITE_MICRO_TEST(TestJustFits) { uint8_t arena[arena_size]; tflite::SimpleTensorAllocator allocator(arena, arena_size); - uint8_t* result = allocator.AllocateMemory(arena_size); + uint8_t* result = allocator.AllocateMemory(arena_size, 1); TF_LITE_MICRO_EXPECT_NE(nullptr, result); } +TF_LITE_MICRO_TEST(TestAligned) { + constexpr size_t arena_size = 1024; + uint8_t arena[arena_size]; + tflite::SimpleTensorAllocator allocator(arena, arena_size); + + uint8_t* result = allocator.AllocateMemory(1, 1); + TF_LITE_MICRO_EXPECT_NE(nullptr, result); + + result = allocator.AllocateMemory(16, 4); + TF_LITE_MICRO_EXPECT_NE(nullptr, result); + TF_LITE_MICRO_EXPECT_EQ(0, reinterpret_cast(result) & 3); +} + +TF_LITE_MICRO_TEST(TestMultipleTooLarge) { + constexpr size_t arena_size = 1024; + uint8_t arena[arena_size]; + tflite::SimpleTensorAllocator allocator(arena, arena_size); + + uint8_t* result = allocator.AllocateMemory(768, 1); + TF_LITE_MICRO_EXPECT_NE(nullptr, result); + + result = allocator.AllocateMemory(768, 1); + TF_LITE_MICRO_EXPECT_EQ(nullptr, result); +} + TF_LITE_MICRO_TESTS_END -- GitLab From 526caa5d9662830d8829e6d2d665b5b463d13f28 Mon Sep 17 00:00:00 2001 From: "A. Unique TensorFlower" Date: Wed, 17 Oct 2018 18:28:35 -0700 Subject: [PATCH 0245/1825] Generalize the Cast(Transpose(*)) -> Transpose(Cast(*)) optimization. PiperOrigin-RevId: 217625758 --- tensorflow/core/grappler/op_types.cc | 15 ++ tensorflow/core/grappler/op_types.h | 4 + .../optimizers/arithmetic_optimizer.cc | 173 +++++++++++------- .../optimizers/arithmetic_optimizer.h | 2 +- .../optimizers/arithmetic_optimizer_test.cc | 158 +++++++++++++--- .../core/grappler/utils/grappler_test.h | 13 +- tensorflow/python/kernel_tests/BUILD | 2 +- 7 files changed, 275 insertions(+), 92 deletions(-) diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc index 0317840a0a..685c5b3705 100644 --- a/tensorflow/core/grappler/op_types.cc +++ b/tensorflow/core/grappler/op_types.cc @@ -73,6 +73,14 @@ bool IsBitcast(const NodeDef& node) { return node.op() == "Bitcast"; } bool IsCast(const NodeDef& node) { return node.op() == "Cast"; } +// TODO(rmlarsen): Add support for "QuantizeDownAndShrinkRange", "Requantize", +// "CompareAndBitpack", "Bucketize" etc. +bool IsCastLike(const NodeDef& node) { + static const gtl::FlatSet* const kCastLikeOps = + CHECK_NOTNULL((new gtl::FlatSet{"Cast"})); + return kCastLikeOps->count(node.op()) > 0; +} + bool IsCheckNumerics(const NodeDef& node) { return node.op() == "CheckNumerics"; } @@ -640,8 +648,15 @@ bool IsValuePreserving(const NodeDef& node) { CHECK_NOTNULL((new gtl::FlatSet{ "InvertPermutation", "Reverse", + "ReverseV2", "Roll", "Transpose", + "DepthToSpace", + "SpaceToDepth", + "BatchToSpace", + "BatchToSpaceND", + "SpaceToBatch", + "SpaceToBatchND", })); return IsValueAndOrderPreserving(node) || kValuePreservingOps->count(node.op()) > 0; diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h index 1a9103e744..b418bb889a 100644 --- a/tensorflow/core/grappler/op_types.h +++ b/tensorflow/core/grappler/op_types.h @@ -213,6 +213,10 @@ bool IsUnaryElementWise(const NodeDef& node); // Returns true if we can find an opdef corresponding to the op of the node. bool HasOpDef(const NodeDef& node); +// Returns true if the op changes the scalar type of its first input elements +// and preserves the number of elements. +bool IsCastLike(const NodeDef& node); + } // end namespace grappler } // end namespace tensorflow diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index a09100f121..655d1e9cfe 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -127,12 +127,10 @@ void SetDataTypeToAttr(DataType dtype, const string& attr_name, NodeDef* node) { } string SourceDataTypeAttrName(const NodeDef& node) { - if (node.op() == "Bitcast") { - return "T"; - } else if (node.op() == "Cast") { + if (node.op() == "Cast") { return "SrcT"; } else { - LOG(FATAL) << "SourceDataTypeAttrName not implemented for op " << node.op(); + return "T"; } } @@ -1853,80 +1851,115 @@ class RemoveRedundantReshape : public ArithmeticOptimizerStage { } }; -// Reorder Cast and Transpose if beneficial. +// Reorder casting and value-preserving ops if beneficial. // -// A common pattern after the layout optimizer is casting an uint8 NHWC -// image to float before transposing it to NCHW. It is beneficial to reorder -// the cast and the transpose to make the transpose process smaller amount -// of data. This optimization converts -// Transpose(Cast(image, dst_type), perm) +// Original motivation: A common pattern after the layout optimizer is +// casting an uint8 NHWC image to float before transposing it to NCHW. It +// is beneficial to reorder the cast and the transpose to make the transpose +// process smaller amount of data. More generally, this optimization converts +// Op(Cast(tensor, dst_type)) +// to +// Cast(Op(tensor), dst_type) +// when sizeof(tensor.type) < sizeof(dst_type), and Op is any value-preserving +// Op, i.e. an op that only reorders the order of the elements in its first +// input. Similarly, this optimization converts +// Cast(Op(tensor), dst_type) // to -// Cast(Transpose(image, perm), dst_type) -// when sizeof(image.type) < sizeof(dst_type). +// Op(Cast(tensor, dst_type)) +// when sizeof(tensor.type) > sizeof(dst_type) // -// TODO(jingyue): This optimization can be generalized to a cast followed by -// a chain of ops that merely reorder elements (e.g. Reshape and -// DepthToSpace). -class ReorderCastAndTranspose : public ArithmeticOptimizerStage { +class ReorderCastLikeAndValuePreserving : public ArithmeticOptimizerStage { public: - explicit ReorderCastAndTranspose(const GraphOptimizerContext& ctx, - const ArithmeticOptimizerContext& ctx_ext) - : ArithmeticOptimizerStage("ReorderCastAndTranspose", ctx, ctx_ext) {} - ~ReorderCastAndTranspose() override = default; + explicit ReorderCastLikeAndValuePreserving( + const GraphOptimizerContext& ctx, + const ArithmeticOptimizerContext& ctx_ext) + : ArithmeticOptimizerStage("ReorderCastLikeAndValuePreserving", ctx, + ctx_ext) {} + ~ReorderCastLikeAndValuePreserving() override = default; bool IsSupported(const NodeDef* node) const override { - return IsTranspose(*node) && NodeIsOnCpuOrGpu(node); + return (IsValuePreserving(*node) || IsCastLike(*node)) && + NodeIsOnCpuOrGpu(node) && !IsControlFlow(*node) && + !IsInPreserveSet(*node); } - Status TrySimplify(NodeDef* node, string* simplified_node_name) override { - const NodeDef* transpose = node; - - // Verify that input to Transpose is the Cast op. - NodeDef* cast; - TF_RETURN_IF_ERROR(GetInputNode(transpose->input(0), &cast)); - if (!IsCast(*cast)) return Status::OK(); - - // Input to the Cast-Transpose chain. - NodeDef* input; - TF_RETURN_IF_ERROR(GetInputNode(cast->input(0), &input)); - - const DataType src_type = GetSourceDataType(*cast); - const DataType dst_type = GetDestinationDataType(*cast); + Status TrySimplify(NodeDef* consumer, string* simplified_node_name) override { + NodeDef* producer; + TF_RETURN_IF_ERROR(GetInputNode(consumer->input(0), &producer)); + const bool producer_is_cast = IsCastLike(*producer); + const bool can_optimize = + ((producer_is_cast && IsValuePreserving(*consumer)) || + (IsValuePreserving(*producer) && IsCastLike(*consumer))); + if (!can_optimize || IsControlFlow(*producer) || + producer->device() != consumer->device()) { + return Status::OK(); + } + const NodeDef* cast_like_node = producer_is_cast ? producer : consumer; + const string src_attr_name = SourceDataTypeAttrName(*cast_like_node); + const string dst_attr_name = DestinationDataTypeAttrName(*cast_like_node); + const DataType src_type = + GetDataTypeFromAttr(*cast_like_node, src_attr_name); + const DataType dst_type = + GetDataTypeFromAttr(*cast_like_node, dst_attr_name); const string src_type_name = DataTypeString(src_type); const string dst_type_name = DataTypeString(dst_type); + if (!IsFixedSizeType(src_type) || !IsFixedSizeType(dst_type)) { + return Status::OK(); + } else if (producer_is_cast && + DataTypeSize(dst_type) <= DataTypeSize(src_type)) { + return Status::OK(); + } else if (!producer_is_cast && + DataTypeSize(dst_type) >= DataTypeSize(src_type)) { + return Status::OK(); + } - // Check if nodes were not already optimized. - const string optimized_cast_name = - OptimizedNodeName(ParseNodeScopeAndName(cast->name()), dst_type_name); - const string optimized_transpose_name = OptimizedNodeName( - ParseNodeScopeAndName(transpose->name()), src_type_name); - - bool is_already_optimized = - ctx().node_map->NodeExists(optimized_transpose_name) || - ctx().node_map->NodeExists(optimized_cast_name); - - if (IsNumberType(src_type) && IsNumberType(dst_type) && - DataTypeSize(src_type) < DataTypeSize(dst_type) && - !is_already_optimized) { - NodeDef* new_transpose = AddCopyNode(optimized_transpose_name, transpose); - (*new_transpose->mutable_attr())["T"].set_type(src_type); - new_transpose->set_input(0, cast->input(0)); - - ctx().node_map->AddOutput(input->name(), new_transpose->name()); - ctx().node_map->AddOutput(NodeName(new_transpose->input(1)), - new_transpose->name()); - - NodeDef* new_cast = AddCopyNode(optimized_cast_name, cast); - new_cast->set_input(0, new_transpose->name()); - ctx().node_map->AddOutput(new_transpose->name(), new_cast->name()); - - AddToOptimizationQueue(new_transpose); - ForwardControlDependencies(new_transpose, {cast, node}); - - *simplified_node_name = new_cast->name(); + // Check that nodes were not already optimized. + const string optimized_producer_name = OptimizedNodeName( + ParseNodeScopeAndName(producer->name()), dst_type_name); + const string optimized_consumer_name = OptimizedNodeName( + ParseNodeScopeAndName(consumer->name()), src_type_name); + const bool is_already_optimized = + ctx().node_map->NodeExists(optimized_consumer_name) || + ctx().node_map->NodeExists(optimized_producer_name); + if (is_already_optimized) { + return Status::OK(); } + // Add copies of consumer and producer in reverse order. + const string new_producer_type_attr_name = + SourceDataTypeAttrName(*consumer); + TF_RETURN_IF_ERROR(CheckAttrExists(*consumer, new_producer_type_attr_name)); + const string new_consumer_type_attr_name = + SourceDataTypeAttrName(*producer); + TF_RETURN_IF_ERROR(CheckAttrExists(*producer, new_consumer_type_attr_name)); + NodeDef* input; + TF_RETURN_IF_ERROR(GetInputNode(producer->input(0), &input)); + // Create new producer node. + NodeDef* new_producer = AddCopyNode(optimized_consumer_name, consumer); + if (IsCheckNumerics(*new_producer) && !IsFloatingType(src_type)) { + new_producer->set_op("Identity"); + new_producer->clear_attr(); + } + new_producer->set_input(0, producer->input(0)); + (*new_producer->mutable_attr())[new_producer_type_attr_name].set_type( + src_type); + ctx().node_map->AddOutput(input->name(), new_producer->name()); + // Create new consumer node. + NodeDef* new_consumer = AddCopyNode(optimized_producer_name, producer); + new_consumer->set_input(0, new_producer->name()); + const DataType new_consumer_type = producer_is_cast ? src_type : dst_type; + if (IsCheckNumerics(*new_producer) && !IsFloatingType(new_consumer_type)) { + new_consumer->set_op("Identity"); + new_consumer->clear_attr(); + } + (*new_consumer->mutable_attr())[new_consumer_type_attr_name].set_type( + new_consumer_type); + ctx().node_map->AddOutput(new_producer->name(), new_consumer->name()); + + AddToOptimizationQueue(new_producer); + *simplified_node_name = new_consumer->name(); + return Status::OK(); } @@ -1944,7 +1977,13 @@ class ReorderCastAndTranspose : public ArithmeticOptimizerStage { (StrContains(device, DEVICE_CPU) || StrContains(device, DEVICE_GPU)); } - bool IsNumberType(DataType dtype) { return kNumberTypes.Contains(dtype); } + bool IsFixedSizeType(DataType dtype) { + return dtype != DT_STRING && dtype != DT_VARIANT && dtype != DT_RESOURCE; + } + + bool IsFloatingType(DataType dtype) { + return kDataTypeIsFloating.Contains(dtype); + } }; // Fold a multiply of a scalar into the following convolution. This folding @@ -3453,8 +3492,8 @@ Status ArithmeticOptimizer::SimplifyArithmeticOps(bool can_use_shapes) { pipeline.AddStage(ctx, ctx_ext); if (options_.remove_logical_not) pipeline.AddStage(ctx, ctx_ext); - if (options_.reorder_cast_and_transpose) - pipeline.AddStage(ctx, ctx_ext); + if (options_.reorder_cast_like_and_value_preserving) + pipeline.AddStage(ctx, ctx_ext); if (options_.simplify_aggregation) pipeline.AddStage(ctx, ctx_ext); if (options_.hoist_cwise_unary_chains) diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h index 15e5ad9df5..e1395d7542 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h @@ -73,7 +73,7 @@ class ArithmeticOptimizer : public GraphOptimizer { bool remove_redundant_bitcast = true; bool remove_redundant_cast = true; bool remove_redundant_reshape = true; - bool reorder_cast_and_transpose = true; + bool reorder_cast_like_and_value_preserving = true; bool replace_mul_with_square = true; bool simplify_aggregation = true; bool convert_pow = true; diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc index d091b26b65..e152948fb2 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc @@ -158,7 +158,7 @@ class ArithmeticOptimizerTest : public GrapplerTest { options.remove_redundant_reshape = false; options.remove_negation = false; options.remove_logical_not = false; - options.reorder_cast_and_transpose = false; + options.reorder_cast_like_and_value_preserving = false; options.replace_mul_with_square = false; options.simplify_aggregation = false; options.unary_ops_composition = false; @@ -231,7 +231,7 @@ class ArithmeticOptimizerTest : public GrapplerTest { void EnableOnlyReorderCastAndTranspose(ArithmeticOptimizer* optimizer) { DisableAllStages(optimizer); - optimizer->options_.reorder_cast_and_transpose = true; + optimizer->options_.reorder_cast_like_and_value_preserving = true; } void EnableOnlyReplaceMulWithSquare(ArithmeticOptimizer* optimizer) { @@ -1320,8 +1320,8 @@ TEST_F(ArithmeticOptimizerTest, RemoveRedundantReshape_CombineReshapes) { test::ExpectTensorEqual(tensors_expected[0], tensors[0]); } -TEST_F(ArithmeticOptimizerTest, ReorderTransposeCast) { - tensorflow::Scope s = tensorflow::Scope::NewRootScope().WithDevice("/gpu:0"); +TEST_F(ArithmeticOptimizerTest, ReorderTransposeCast_ProducerIsCast) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope().WithDevice("/CPU:0"); Output nhwc_uint8 = ops::Placeholder(s, DT_UINT8, ops::Placeholder::Shape({8, 28, 28, 3})); Output nhwc_fp32 = ops::Cast(s, nhwc_uint8, DT_FLOAT); @@ -1333,11 +1333,14 @@ TEST_F(ArithmeticOptimizerTest, ReorderTransposeCast) { item.fetch = {"outputs"}; TF_CHECK_OK(s.ToGraphDef(&item.graph)); - GraphDef output; - TF_EXPECT_OK(ArithmeticOptimizer().Optimize(nullptr, item, &output)); + auto input_t = GenerateRandomTensor(TensorShape({8, 28, 28, 3})); + auto tensors_expected = + EvaluateNodes(item.graph, item.fetch, {{"Placeholder", input_t}}); + EXPECT_EQ(1, tensors_expected.size()); - item.graph.Swap(&output); - TF_EXPECT_OK(ModelPruner().Optimize(nullptr, item, &output)); + GraphDef output; + ArithmeticOptimizer optimizer; + OptimizeAndPrune(&optimizer, &item, &output); const NodeDef* transpose_node = nullptr; for (const NodeDef& node : output.node()) { @@ -1354,36 +1357,147 @@ TEST_F(ArithmeticOptimizerTest, ReorderTransposeCast) { EXPECT_EQ(NodeName(node.input(0)), transpose_node->name()); } } + + auto tensors = + EvaluateNodes(item.graph, item.fetch, {{"Placeholder", input_t}}); + EXPECT_EQ(1, tensors.size()); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); } -TEST_F(ArithmeticOptimizerTest, NoReorderTransposeCast) { - tensorflow::Scope s = tensorflow::Scope::NewRootScope().WithDevice("/gpu:0"); +TEST_F(ArithmeticOptimizerTest, ReorderTransposeCast_ProducerIsTranspose) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope().WithDevice("/CPU:0"); Output nhwc_fp32 = ops::Placeholder(s, DT_FLOAT, ops::Placeholder::Shape({8, 28, 28, 3})); - Output nhwc_uint8 = ops::Cast(s, nhwc_fp32, DT_UINT8); - Output nchw_uint8 = - ops::Transpose(s, nhwc_uint8, ops::Const(s, {0, 3, 1, 2}, {4})); + Output nchw_fp32 = + ops::Transpose(s, nhwc_fp32, ops::Const(s, {0, 3, 1, 2}, {4})); + Output nchw_uint8 = ops::Cast(s, nchw_fp32, DT_UINT8); Output outputs = ops::Identity(s.WithOpName("outputs"), nchw_uint8); GrapplerItem item; item.fetch = {"outputs"}; TF_CHECK_OK(s.ToGraphDef(&item.graph)); + auto input_t = + GenerateConstantTensor(TensorShape({8, 28, 28, 3}), 42.0f); + auto tensors_expected = + EvaluateNodes(item.graph, item.fetch, {{"Placeholder", input_t}}); + EXPECT_EQ(1, tensors_expected.size()); + GraphDef output; - TF_EXPECT_OK(ArithmeticOptimizer().Optimize(nullptr, item, &output)); + ArithmeticOptimizer optimizer; + OptimizeAndPrune(&optimizer, &item, &output); - item.graph.Swap(&output); - TF_EXPECT_OK(ModelPruner().Optimize(nullptr, item, &output)); + const NodeDef* cast_node = nullptr; + for (const NodeDef& node : output.node()) { + if (node.op() == "Cast") { + EXPECT_EQ(cast_node, nullptr); + cast_node = &node; + EXPECT_EQ(NodeName(node.input(0)), "Placeholder"); + } + } + EXPECT_NE(cast_node, nullptr); + + for (const NodeDef& node : output.node()) { + if (node.op() == "Transpose") { + EXPECT_EQ(DT_UINT8, node.attr().at("T").type()); + EXPECT_EQ(NodeName(node.input(0)), cast_node->name()); + } + } + + auto tensors = + EvaluateNodes(item.graph, item.fetch, {{"Placeholder", input_t}}); + EXPECT_EQ(1, tensors.size()); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); +} + +TEST_F(ArithmeticOptimizerTest, ReorderTransposeReverseCast) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope().WithDevice("/CPU:0"); + Output nhwc_uint8 = + ops::Placeholder(s, DT_UINT8, ops::Placeholder::Shape({8, 28, 28, 3})); + Output nhwc_fp32 = ops::Cast(s, nhwc_uint8, DT_FLOAT); + Output nhwc_fp32_reversed = + ops::Reverse(s, nhwc_fp32, ops::Const(s, {0}, {1})); + Output nchw_fp32_reversed = + ops::Transpose(s, nhwc_fp32_reversed, ops::Const(s, {0, 3, 1, 2}, {4})); + + Output outputs = ops::Identity(s.WithOpName("outputs"), nchw_fp32_reversed); + + GrapplerItem item; + item.fetch = {"outputs"}; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + + auto input_t = GenerateRandomTensor(TensorShape({8, 28, 28, 3})); + auto tensors_expected = + EvaluateNodes(item.graph, item.fetch, {{"Placeholder", input_t}}); + EXPECT_EQ(1, tensors_expected.size()); + + GraphDef output; + ArithmeticOptimizer optimizer; + OptimizeAndPrune(&optimizer, &item, &output); - int num_transposes = 0; + const NodeDef* reverse_node = nullptr; + const NodeDef* transpose_node = nullptr; + const NodeDef* cast_node = nullptr; for (const NodeDef& node : output.node()) { if (node.op() == "Transpose") { + EXPECT_EQ(transpose_node, nullptr); EXPECT_EQ(DT_UINT8, node.attr().at("T").type()); - EXPECT_EQ(node.input(0), "Cast"); - ++num_transposes; + transpose_node = &node; + } else if (node.op() == "ReverseV2") { + EXPECT_EQ(reverse_node, nullptr); + EXPECT_EQ(DT_UINT8, node.attr().at("T").type()); + reverse_node = &node; + } else if (node.op() == "Cast") { + cast_node = &node; } } - EXPECT_EQ(1, num_transposes); + EXPECT_NE(cast_node, nullptr); + EXPECT_NE(reverse_node, nullptr); + EXPECT_NE(transpose_node, nullptr); + EXPECT_EQ(NodeName(reverse_node->input(0)), "Placeholder"); + EXPECT_EQ(NodeName(transpose_node->input(0)), reverse_node->name()); + EXPECT_EQ(NodeName(cast_node->input(0)), transpose_node->name()); + + auto tensors = + EvaluateNodes(item.graph, item.fetch, {{"Placeholder", input_t}}); + EXPECT_EQ(1, tensors.size()); + test::ExpectTensorEqual(tensors_expected[0], tensors[0]); +} + +TEST_F(ArithmeticOptimizerTest, NoReorderTransposeCast_ProducerIsCast) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope().WithDevice("/CPU:0"); + Output nhwc_fp32 = + ops::Placeholder(s, DT_FLOAT, ops::Placeholder::Shape({8, 28, 28, 3})); + Output nhwc_uint8 = ops::Cast(s, nhwc_fp32, DT_UINT8); + Output nchw_uint8 = + ops::Transpose(s, nhwc_uint8, ops::Const(s, {0, 3, 1, 2}, {4})); + Output outputs = ops::Identity(s.WithOpName("outputs"), nchw_uint8); + + GrapplerItem item; + item.fetch = {"outputs"}; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + + GraphDef output; + TF_EXPECT_OK(ArithmeticOptimizer().Optimize(nullptr, item, &output)); + CompareGraphs(item.graph, output); +} + +TEST_F(ArithmeticOptimizerTest, NoReorderTransposeCast_ProducerIsTranspose) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope().WithDevice("/CPU:0"); + Output nhwc_uint8 = + ops::Placeholder(s, DT_UINT8, ops::Placeholder::Shape({8, 28, 28, 3})); + Output nchw_uint8 = + ops::Transpose(s, nhwc_uint8, ops::Const(s, {0, 3, 1, 2}, {4})); + Output nchw_fp32 = ops::Cast(s, nchw_uint8, DT_FLOAT); + Output outputs = ops::Identity(s.WithOpName("outputs"), nchw_fp32); + + GrapplerItem item; + item.fetch = {"outputs"}; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + + GraphDef output; + TF_EXPECT_OK(ArithmeticOptimizer().Optimize(nullptr, item, &output)); + CompareGraphs(item.graph, output); } TEST_F(ArithmeticOptimizerTest, RemoveIdentityTransposes) { @@ -1704,11 +1818,11 @@ TEST_F(ArithmeticOptimizerTest, OptimizeCastMulTransposeConv) { GraphDef output; ArithmeticOptimizer optimizer; // all optimization stages are on OptimizeTwiceAndPrune(&optimizer, &item, &output, /*const_folding=*/true); - + LOG(INFO) << output.DebugString(); NodeMap node_map(&output); // Expected names for reordered cast and transpose. - const string p = "ArithmeticOptimizer/ReorderCastAndTranspose_"; + const string p = "ArithmeticOptimizer/ReorderCastLikeAndValuePreserving_"; const string optimized_cast_name = strings::StrCat(p, "float_Cast"); const string optimized_transpose_name = strings::StrCat(p, "uint8_Transpose"); diff --git a/tensorflow/core/grappler/utils/grappler_test.h b/tensorflow/core/grappler/utils/grappler_test.h index bd4d7f2a7e..0cfd740dcb 100644 --- a/tensorflow/core/grappler/utils/grappler_test.h +++ b/tensorflow/core/grappler/utils/grappler_test.h @@ -58,7 +58,7 @@ class GrapplerTest : public ::testing::Test { // Count nodes of the given op-type in a graph. int CountOpNodes(const GraphDef& graph, const string& op); - // Get a random tansor with given shape. + // Get a random tensor with given shape. template Tensor GenerateRandomTensor(const TensorShape& shape) const { typedef typename EnumToDataType::Type T; @@ -68,6 +68,17 @@ class GrapplerTest : public ::testing::Test { return tensor; } + // Get a constant tensor with given shape. + template + Tensor GenerateConstantTensor( + const TensorShape& shape, + typename EnumToDataType::Type value) const { + typedef typename EnumToDataType::Type T; + Tensor tensor(DTYPE, shape); + for (auto i = 0; i < tensor.NumElements(); i++) tensor.flat()(i) = value; + return tensor; + } + private: SessionOptions options_; }; diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index 3d9b886ebb..cf0b23a6ef 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -607,7 +607,7 @@ tf_py_test( tf_py_test( name = "matrix_exponential_op_test", - size = "small", + size = "medium", srcs = ["matrix_exponential_op_test.py"], additional_deps = [ "//third_party/py/numpy", -- GitLab From aaaf1908758c2c1fadf534b6dccf6fdea1bdc18b Mon Sep 17 00:00:00 2001 From: Asim Shankar Date: Wed, 17 Oct 2018 18:41:54 -0700 Subject: [PATCH 0246/1825] TF 2.0: tf.Session -> tf.compat.v1.Session (In the spirit of https://github.com/tensorflow/community/pull/20) PiperOrigin-RevId: 217627136 --- tensorflow/python/client/session.py | 4 +- tensorflow/python/framework/ops.py | 2 +- tensorflow/python/ops/session_ops.py | 6 +- .../v2/tensorflow.-interactive-session.pbtxt | 51 ----------------- .../api/golden/v2/tensorflow.-session.pbtxt | 55 ------------------- .../tools/api/golden/v2/tensorflow.pbtxt | 24 -------- 6 files changed, 6 insertions(+), 136 deletions(-) delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-interactive-session.pbtxt delete mode 100644 tensorflow/tools/api/golden/v2/tensorflow.-session.pbtxt diff --git a/tensorflow/python/client/session.py b/tensorflow/python/client/session.py index 7f783286d3..06c66dda9f 100644 --- a/tensorflow/python/client/session.py +++ b/tensorflow/python/client/session.py @@ -1471,7 +1471,7 @@ class BaseSession(SessionInterface): return BaseSession._Callable(self, callable_options) -@tf_export('Session') +@tf_export(v1=['Session']) class Session(BaseSession): """A class for running TensorFlow operations. @@ -1626,7 +1626,7 @@ class Session(BaseSession): tf_session.TF_Reset(target, containers, config) -@tf_export('InteractiveSession') +@tf_export(v1=['InteractiveSession']) class InteractiveSession(BaseSession): """A TensorFlow `Session` for use in interactive contexts, such as a shell. diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index e6ffc3d19c..a8dff3109f 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -5098,7 +5098,7 @@ def default_session(session): return _default_session_stack.get_controller(session) -@tf_export("get_default_session") +@tf_export(v1=["get_default_session"]) def get_default_session(): """Returns the default session for the current thread. diff --git a/tensorflow/python/ops/session_ops.py b/tensorflow/python/ops/session_ops.py index e229501c10..720be098c2 100644 --- a/tensorflow/python/ops/session_ops.py +++ b/tensorflow/python/ops/session_ops.py @@ -140,7 +140,7 @@ class TensorHandle(object): return feeder.op.name + ";" + TensorHandle._get_reader_key(handle) -@tf_export("get_session_handle") +@tf_export(v1=["get_session_handle"]) def get_session_handle(data, name=None): """Return the handle of `data`. @@ -183,7 +183,7 @@ def get_session_handle(data, name=None): return gen_data_flow_ops.get_session_handle(data, name=name) -@tf_export("get_session_tensor") +@tf_export(v1=["get_session_tensor"]) def get_session_tensor(handle, dtype, name=None): """Get the tensor of type `dtype` by feeding a tensor handle. @@ -224,7 +224,7 @@ def get_session_tensor(handle, dtype, name=None): return (holder, tensor) -@tf_export("delete_session_tensor") +@tf_export(v1=["delete_session_tensor"]) def delete_session_tensor(handle, name=None): """Delete the tensor for the given tensor handle. diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-interactive-session.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-interactive-session.pbtxt deleted file mode 100644 index 0a3b81bf82..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-interactive-session.pbtxt +++ /dev/null @@ -1,51 +0,0 @@ -path: "tensorflow.InteractiveSession" -tf_class { - is_instance: "" - is_instance: "" - is_instance: "" - is_instance: "" - member { - name: "graph" - mtype: "" - } - member { - name: "graph_def" - mtype: "" - } - member { - name: "sess_str" - mtype: "" - } - member_method { - name: "__init__" - argspec: "args=[\'self\', \'target\', \'graph\', \'config\'], varargs=None, keywords=None, defaults=[\'\', \'None\', \'None\'], " - } - member_method { - name: "as_default" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "close" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "list_devices" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "make_callable" - argspec: "args=[\'self\', \'fetches\', \'feed_list\', \'accept_options\'], varargs=None, keywords=None, defaults=[\'None\', \'False\'], " - } - member_method { - name: "partial_run" - argspec: "args=[\'self\', \'handle\', \'fetches\', \'feed_dict\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "partial_run_setup" - argspec: "args=[\'self\', \'fetches\', \'feeds\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "run" - argspec: "args=[\'self\', \'fetches\', \'feed_dict\', \'options\', \'run_metadata\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.-session.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.-session.pbtxt deleted file mode 100644 index 1d6b037f9c..0000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.-session.pbtxt +++ /dev/null @@ -1,55 +0,0 @@ -path: "tensorflow.Session" -tf_class { - is_instance: "" - is_instance: "" - is_instance: "" - is_instance: "" - member { - name: "graph" - mtype: "" - } - member { - name: "graph_def" - mtype: "" - } - member { - name: "sess_str" - mtype: "" - } - member_method { - name: "__init__" - argspec: "args=[\'self\', \'target\', \'graph\', \'config\'], varargs=None, keywords=None, defaults=[\'\', \'None\', \'None\'], " - } - member_method { - name: "as_default" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "close" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "list_devices" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "make_callable" - argspec: "args=[\'self\', \'fetches\', \'feed_list\', \'accept_options\'], varargs=None, keywords=None, defaults=[\'None\', \'False\'], " - } - member_method { - name: "partial_run" - argspec: "args=[\'self\', \'handle\', \'fetches\', \'feed_dict\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "partial_run_setup" - argspec: "args=[\'self\', \'fetches\', \'feeds\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "reset" - argspec: "args=[\'target\', \'containers\', \'config\'], varargs=None, keywords=None, defaults=[\'None\', \'None\'], " - } - member_method { - name: "run" - argspec: "args=[\'self\', \'fetches\', \'feed_dict\', \'options\', \'run_metadata\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'None\'], " - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt index 4b4d150aa1..ae0324a418 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.pbtxt @@ -80,10 +80,6 @@ tf_module { name: "IndexedSlices" mtype: "" } - member { - name: "InteractiveSession" - mtype: "" - } member { name: "LogMessage" mtype: "" @@ -124,10 +120,6 @@ tf_module { name: "RunOptions" mtype: "" } - member { - name: "Session" - mtype: "" - } member { name: "SessionLog" mtype: "" @@ -756,10 +748,6 @@ tf_module { name: "decode_raw" argspec: "args=[\'bytes\', \'out_type\', \'little_endian\', \'name\'], varargs=None, keywords=None, defaults=[\'True\', \'None\'], " } - member_method { - name: "delete_session_tensor" - argspec: "args=[\'handle\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " - } member_method { name: "dequantize" argspec: "args=[\'input\', \'min_range\', \'max_range\', \'mode\', \'name\'], varargs=None, keywords=None, defaults=[\'MIN_COMBINED\', \'None\'], " @@ -940,18 +928,6 @@ tf_module { name: "get_default_graph" argspec: "args=[], varargs=None, keywords=None, defaults=None" } - member_method { - name: "get_default_session" - argspec: "args=[], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "get_session_handle" - argspec: "args=[\'data\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " - } - member_method { - name: "get_session_tensor" - argspec: "args=[\'handle\', \'dtype\', \'name\'], varargs=None, keywords=None, defaults=[\'None\'], " - } member_method { name: "gradients" argspec: "args=[\'ys\', \'xs\', \'grad_ys\', \'name\', \'colocate_gradients_with_ops\', \'gate_gradients\', \'aggregation_method\', \'stop_gradients\', \'unconnected_gradients\'], varargs=None, keywords=None, defaults=[\'None\', \'gradients\', \'False\', \'False\', \'None\', \'None\', \'UnconnectedGradients.NONE\'], " -- GitLab From 2a81cf99d22c3a0d551e68805bb4b021fbfcb860 Mon Sep 17 00:00:00 2001 From: Asim Shankar Date: Wed, 17 Oct 2018 20:35:29 -0700 Subject: [PATCH 0247/1825] [Java]: Release 1.12.0-rc1 PiperOrigin-RevId: 217636182 --- tensorflow/java/maven/libtensorflow/pom.xml | 2 +- tensorflow/java/maven/libtensorflow_jni/pom.xml | 2 +- tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml | 2 +- tensorflow/java/maven/pom.xml | 2 +- tensorflow/java/maven/proto/pom.xml | 2 +- tensorflow/java/maven/spark-tensorflow-connector/pom.xml | 2 +- tensorflow/java/maven/tensorflow-hadoop/pom.xml | 2 +- tensorflow/java/maven/tensorflow/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tensorflow/java/maven/libtensorflow/pom.xml b/tensorflow/java/maven/libtensorflow/pom.xml index 041e4778cd..c25aa714cc 100644 --- a/tensorflow/java/maven/libtensorflow/pom.xml +++ b/tensorflow/java/maven/libtensorflow/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.12.0-rc0 + 1.12.0-rc1 ../ libtensorflow diff --git a/tensorflow/java/maven/libtensorflow_jni/pom.xml b/tensorflow/java/maven/libtensorflow_jni/pom.xml index b4ccf2d77c..7b47fbbce4 100644 --- a/tensorflow/java/maven/libtensorflow_jni/pom.xml +++ b/tensorflow/java/maven/libtensorflow_jni/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.12.0-rc0 + 1.12.0-rc1 ../ libtensorflow_jni diff --git a/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml b/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml index 09adfb2b57..97a25ac5e7 100644 --- a/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml +++ b/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.12.0-rc0 + 1.12.0-rc1 ../ libtensorflow_jni_gpu diff --git a/tensorflow/java/maven/pom.xml b/tensorflow/java/maven/pom.xml index d7fe50ce26..b04d889db6 100644 --- a/tensorflow/java/maven/pom.xml +++ b/tensorflow/java/maven/pom.xml @@ -6,7 +6,7 @@ 4.0.0 org.tensorflow parentpom - 1.12.0-rc0 + 1.12.0-rc1 pom https://www.tensorflow.org diff --git a/tensorflow/java/maven/proto/pom.xml b/tensorflow/java/maven/proto/pom.xml index 8bbe834eba..7dd360b194 100644 --- a/tensorflow/java/maven/proto/pom.xml +++ b/tensorflow/java/maven/proto/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.12.0-rc0 + 1.12.0-rc1 ../ proto diff --git a/tensorflow/java/maven/spark-tensorflow-connector/pom.xml b/tensorflow/java/maven/spark-tensorflow-connector/pom.xml index b31510f637..c9e1457655 100644 --- a/tensorflow/java/maven/spark-tensorflow-connector/pom.xml +++ b/tensorflow/java/maven/spark-tensorflow-connector/pom.xml @@ -6,7 +6,7 @@ org.tensorflow spark-tensorflow-connector_2.11 jar - 1.12.0-rc0 + 1.12.0-rc1 spark-tensorflow-connector https://www.tensorflow.org TensorFlow TFRecord connector for Apache Spark DataFrames diff --git a/tensorflow/java/maven/tensorflow-hadoop/pom.xml b/tensorflow/java/maven/tensorflow-hadoop/pom.xml index 8b551e24f1..e2e0d8278c 100644 --- a/tensorflow/java/maven/tensorflow-hadoop/pom.xml +++ b/tensorflow/java/maven/tensorflow-hadoop/pom.xml @@ -5,7 +5,7 @@ org.tensorflow tensorflow-hadoop jar - 1.12.0-rc0 + 1.12.0-rc1 tensorflow-hadoop https://www.tensorflow.org TensorFlow TFRecord InputFormat/OutputFormat for Apache Hadoop diff --git a/tensorflow/java/maven/tensorflow/pom.xml b/tensorflow/java/maven/tensorflow/pom.xml index 60fcc98bf5..896ac068f3 100644 --- a/tensorflow/java/maven/tensorflow/pom.xml +++ b/tensorflow/java/maven/tensorflow/pom.xml @@ -6,7 +6,7 @@ org.tensorflow parentpom - 1.12.0-rc0 + 1.12.0-rc1 ../ tensorflow -- GitLab From c3b42c815ca49461bb667b667546690e0d4200d2 Mon Sep 17 00:00:00 2001 From: Michael Case Date: Wed, 17 Oct 2018 20:40:09 -0700 Subject: [PATCH 0248/1825] Automated rollback of commit 20f03388ac28fdf5ad33adb87d95346209ef0052 PiperOrigin-RevId: 217636584 --- tensorflow/BUILD | 1 - tensorflow/api_template.__init__.py | 17 +- tensorflow/contrib/estimator/BUILD | 337 +- tensorflow/contrib/estimator/__init__.py | 47 +- .../estimator/python/estimator/baseline.py | 92 +- .../python/estimator/baseline_test.py | 436 -- .../python/estimator/boosted_trees.py | 419 +- .../python/estimator/boosted_trees_test.py | 438 -- .../contrib/estimator/python/estimator/dnn.py | 156 +- .../python/estimator/dnn_linear_combined.py | 176 +- .../estimator/dnn_linear_combined_test.py | 227 - .../estimator/python/estimator/dnn_test.py | 171 - .../estimator/dnn_with_layer_annotations.py | 430 +- .../dnn_with_layer_annotations_test.py | 611 --- .../python/estimator/early_stopping.py | 498 +- .../python/estimator/early_stopping_test.py | 246 - .../estimator/python/estimator/export.py | 213 +- .../estimator/python/estimator/export_test.py | 373 -- .../estimator/python/estimator/exporter.py | 270 +- .../python/estimator/exporter_test.py | 206 - .../estimator/python/estimator/extenders.py | 349 +- .../python/estimator/extenders_test.py | 426 -- .../estimator/python/estimator/head.py | 969 +--- .../estimator/python/estimator/head_test.py | 1482 ------ .../estimator/python/estimator/hooks.py | 277 +- .../estimator/python/estimator/hooks_test.py | 403 -- .../estimator/python/estimator/linear.py | 130 +- .../estimator/python/estimator/linear_test.py | 156 - .../estimator/python/estimator/logit_fns.py | 86 +- .../python/estimator/logit_fns_test.py | 95 - .../estimator/python/estimator/multi_head.py | 416 +- .../python/estimator/multi_head_test.py | 705 --- .../python/estimator/replicate_model_fn.py | 820 +--- .../estimator/replicate_model_fn_test.py | 1649 ------- .../contrib/estimator/python/estimator/rnn.py | 572 +-- .../estimator/python/estimator/rnn_test.py | 1185 ----- .../python/estimator/saved_model_estimator.py | 441 +- .../estimator/saved_model_estimator_test.py | 369 -- tensorflow/python/estimator/BUILD | 424 +- tensorflow/python/estimator/__init__.py | 17 +- .../python/estimator/canned/__init__.py | 32 + .../python/estimator/canned/baseline.py | 366 +- .../python/estimator/canned/baseline_test.py | 1558 ------- .../python/estimator/canned/boosted_trees.py | 1558 +------ .../estimator/canned/boosted_trees_test.py | 2549 ----------- .../estimator/canned/boosted_trees_utils.py | 72 +- .../canned/boosted_trees_utils_test.py | 187 - tensorflow/python/estimator/canned/dnn.py | 652 +-- .../estimator/canned/dnn_linear_combined.py | 626 +-- .../canned/dnn_linear_combined_test.py | 1123 ----- .../python/estimator/canned/dnn_test.py | 580 --- .../estimator/canned/dnn_testing_utils.py | 2068 +-------- tensorflow/python/estimator/canned/head.py | 1593 +------ .../python/estimator/canned/head_test.py | 4056 ----------------- tensorflow/python/estimator/canned/linear.py | 535 +-- .../python/estimator/canned/linear_test.py | 255 -- .../estimator/canned/linear_testing_utils.py | 2349 +--------- .../python/estimator/canned/metric_keys.py | 46 +- .../python/estimator/canned/optimizers.py | 72 +- .../estimator/canned/optimizers_test.py | 103 - .../python/estimator/canned/parsing_utils.py | 296 +- .../estimator/canned/parsing_utils_test.py | 211 - .../estimator/canned/prediction_keys.py | 29 +- tensorflow/python/estimator/estimator.py | 2172 +-------- tensorflow/python/estimator/estimator_lib.py | 45 +- tensorflow/python/estimator/estimator_test.py | 3341 -------------- .../python/estimator/export/__init__.py | 32 + tensorflow/python/estimator/export/export.py | 628 +-- .../python/estimator/export/export_lib.py | 24 +- .../python/estimator/export/export_output.py | 407 +- .../estimator/export/export_output_test.py | 397 -- .../python/estimator/export/export_test.py | 802 ---- tensorflow/python/estimator/exporter.py | 498 +- tensorflow/python/estimator/exporter_test.py | 400 -- tensorflow/python/estimator/gc.py | 199 +- tensorflow/python/estimator/gc_test.py | 156 - .../python/estimator/inputs/__init__.py | 32 + tensorflow/python/estimator/inputs/inputs.py | 19 +- .../python/estimator/inputs/numpy_io.py | 217 +- .../python/estimator/inputs/numpy_io_test.py | 620 --- .../python/estimator/inputs/pandas_io.py | 147 +- .../python/estimator/inputs/pandas_io_test.py | 320 -- .../estimator/inputs/queues/__init__.py | 32 + .../inputs/queues/feeding_functions.py | 507 +-- .../inputs/queues/feeding_functions_test.py | 391 -- .../inputs/queues/feeding_queue_runner.py | 172 +- .../queues/feeding_queue_runner_test.py | 140 - tensorflow/python/estimator/keras.py | 492 +- tensorflow/python/estimator/keras_test.py | 805 ---- tensorflow/python/estimator/model_fn.py | 510 +-- tensorflow/python/estimator/model_fn_test.py | 661 --- tensorflow/python/estimator/run_config.py | 907 +--- .../python/estimator/run_config_test.py | 1236 ----- tensorflow/python/estimator/training.py | 1065 +---- tensorflow/python/estimator/training_test.py | 2198 --------- tensorflow/python/estimator/util.py | 157 +- tensorflow/python/estimator/util_test.py | 102 - tensorflow/python/feature_column/BUILD | 2 +- tensorflow/python/tools/api/generator/BUILD | 18 - ...rflow.estimator.-baseline-classifier.pbtxt | 4 +- ...orflow.estimator.-baseline-regressor.pbtxt | 4 +- .../tensorflow.estimator.-best-exporter.pbtxt | 4 +- ....estimator.-boosted-trees-classifier.pbtxt | 6 +- ...w.estimator.-boosted-trees-regressor.pbtxt | 6 +- ...nsorflow.estimator.-d-n-n-classifier.pbtxt | 4 +- ...or.-d-n-n-linear-combined-classifier.pbtxt | 4 +- ...tor.-d-n-n-linear-combined-regressor.pbtxt | 4 +- ...ensorflow.estimator.-d-n-n-regressor.pbtxt | 4 +- ...tensorflow.estimator.-estimator-spec.pbtxt | 4 +- .../v1/tensorflow.estimator.-estimator.pbtxt | 2 +- .../v1/tensorflow.estimator.-eval-spec.pbtxt | 4 +- .../v1/tensorflow.estimator.-exporter.pbtxt | 2 +- ...tensorflow.estimator.-final-exporter.pbtxt | 4 +- ...ensorflow.estimator.-latest-exporter.pbtxt | 4 +- ...sorflow.estimator.-linear-classifier.pbtxt | 4 +- ...nsorflow.estimator.-linear-regressor.pbtxt | 4 +- .../v1/tensorflow.estimator.-mode-keys.pbtxt | 2 +- .../v1/tensorflow.estimator.-run-config.pbtxt | 2 +- .../v1/tensorflow.estimator.-train-spec.pbtxt | 4 +- ...rflow.estimator.-warm-start-settings.pbtxt | 4 +- ...imator.export.-classification-output.pbtxt | 4 +- ...flow.estimator.export.-export-output.pbtxt | 2 +- ...low.estimator.export.-predict-output.pbtxt | 4 +- ....estimator.export.-regression-output.pbtxt | 4 +- ...mator.export.-serving-input-receiver.pbtxt | 4 +- ...xport.-tensor-serving-input-receiver.pbtxt | 4 +- ...rflow.estimator.-baseline-classifier.pbtxt | 4 +- ...orflow.estimator.-baseline-regressor.pbtxt | 4 +- .../tensorflow.estimator.-best-exporter.pbtxt | 4 +- ....estimator.-boosted-trees-classifier.pbtxt | 6 +- ...w.estimator.-boosted-trees-regressor.pbtxt | 6 +- ...nsorflow.estimator.-d-n-n-classifier.pbtxt | 4 +- ...or.-d-n-n-linear-combined-classifier.pbtxt | 4 +- ...tor.-d-n-n-linear-combined-regressor.pbtxt | 4 +- ...ensorflow.estimator.-d-n-n-regressor.pbtxt | 4 +- ...tensorflow.estimator.-estimator-spec.pbtxt | 4 +- .../v2/tensorflow.estimator.-estimator.pbtxt | 2 +- .../v2/tensorflow.estimator.-eval-spec.pbtxt | 4 +- .../v2/tensorflow.estimator.-exporter.pbtxt | 2 +- ...tensorflow.estimator.-final-exporter.pbtxt | 4 +- ...ensorflow.estimator.-latest-exporter.pbtxt | 4 +- ...sorflow.estimator.-linear-classifier.pbtxt | 4 +- ...nsorflow.estimator.-linear-regressor.pbtxt | 4 +- .../v2/tensorflow.estimator.-mode-keys.pbtxt | 2 +- .../v2/tensorflow.estimator.-run-config.pbtxt | 2 +- .../v2/tensorflow.estimator.-train-spec.pbtxt | 4 +- ...rflow.estimator.-warm-start-settings.pbtxt | 4 +- ...imator.export.-classification-output.pbtxt | 4 +- ...flow.estimator.export.-export-output.pbtxt | 2 +- ...low.estimator.export.-predict-output.pbtxt | 4 +- ....estimator.export.-regression-output.pbtxt | 4 +- ...mator.export.-serving-input-receiver.pbtxt | 4 +- ...xport.-tensor-serving-input-receiver.pbtxt | 4 +- tensorflow/tools/pip_package/setup.py | 1 + 154 files changed, 919 insertions(+), 56388 deletions(-) delete mode 100644 tensorflow/contrib/estimator/python/estimator/baseline_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/dnn_linear_combined_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/dnn_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/dnn_with_layer_annotations_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/early_stopping_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/export_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/exporter_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/extenders_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/head_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/hooks_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/linear_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/logit_fns_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/multi_head_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/replicate_model_fn_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/rnn_test.py delete mode 100644 tensorflow/contrib/estimator/python/estimator/saved_model_estimator_test.py delete mode 100644 tensorflow/python/estimator/canned/baseline_test.py delete mode 100644 tensorflow/python/estimator/canned/boosted_trees_test.py delete mode 100644 tensorflow/python/estimator/canned/boosted_trees_utils_test.py delete mode 100644 tensorflow/python/estimator/canned/dnn_linear_combined_test.py delete mode 100644 tensorflow/python/estimator/canned/dnn_test.py delete mode 100644 tensorflow/python/estimator/canned/head_test.py delete mode 100644 tensorflow/python/estimator/canned/linear_test.py delete mode 100644 tensorflow/python/estimator/canned/optimizers_test.py delete mode 100644 tensorflow/python/estimator/canned/parsing_utils_test.py delete mode 100644 tensorflow/python/estimator/estimator_test.py delete mode 100644 tensorflow/python/estimator/export/export_output_test.py delete mode 100644 tensorflow/python/estimator/export/export_test.py delete mode 100644 tensorflow/python/estimator/exporter_test.py delete mode 100644 tensorflow/python/estimator/gc_test.py delete mode 100644 tensorflow/python/estimator/inputs/numpy_io_test.py delete mode 100644 tensorflow/python/estimator/inputs/pandas_io_test.py delete mode 100644 tensorflow/python/estimator/inputs/queues/feeding_functions_test.py delete mode 100644 tensorflow/python/estimator/inputs/queues/feeding_queue_runner_test.py delete mode 100644 tensorflow/python/estimator/keras_test.py delete mode 100644 tensorflow/python/estimator/model_fn_test.py delete mode 100644 tensorflow/python/estimator/run_config_test.py delete mode 100644 tensorflow/python/estimator/training_test.py delete mode 100644 tensorflow/python/estimator/util_test.py diff --git a/tensorflow/BUILD b/tensorflow/BUILD index 77e3baaff1..82526cead4 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -581,7 +581,6 @@ gen_api_init_files( py_library( name = "tensorflow_py", - srcs = ["//tensorflow/python/estimator/api:estimator_python_api_gen"], srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = [ diff --git a/tensorflow/api_template.__init__.py b/tensorflow/api_template.__init__.py index 2de740e145..65172fd74a 100644 --- a/tensorflow/api_template.__init__.py +++ b/tensorflow/api_template.__init__.py @@ -23,18 +23,11 @@ import os as _os # pylint: disable=g-bad-import-order from tensorflow.python import pywrap_tensorflow # pylint: disable=unused-import -try: - # Add `estimator` attribute to allow access to estimator APIs via - # "tf.estimator..." - from tensorflow.python.estimator.api import estimator # pylint: disable=g-import-not-at-top - - # Add `estimator` to the __path__ to allow "from tensorflow.estimator..." - # style imports. - from tensorflow.python.estimator import api as estimator_api # pylint: disable=g-import-not-at-top - __path__ += [_os.path.dirname(estimator_api.__file__)] - del estimator_api -except (ImportError, AttributeError): - print('tf.estimator package not installed.') +from tensorflow.python.tools import component_api_helper +component_api_helper.package_hook( + parent_package_str=__name__, + child_package_str=('tensorflow_estimator.python.estimator.api.estimator')) +del component_api_helper # API IMPORTS PLACEHOLDER diff --git a/tensorflow/contrib/estimator/BUILD b/tensorflow/contrib/estimator/BUILD index 1ea00fb7f3..8b99158b30 100644 --- a/tensorflow/contrib/estimator/BUILD +++ b/tensorflow/contrib/estimator/BUILD @@ -8,6 +8,7 @@ licenses(["notice"]) # Apache 2.0 load("//tensorflow:tensorflow.bzl", "py_test") load("//tensorflow:tensorflow.bzl", "cuda_py_test") +# PLACEHOLDER PIP REQUIREMENTS py_library( name = "estimator_py", @@ -20,6 +21,7 @@ py_library( ":dnn_linear_combined", ":dnn_with_layer_annotations", ":early_stopping", + ":expect_tensorflow_estimator_installed", ":export", ":exporter", ":extenders", @@ -32,6 +34,7 @@ py_library( ":rnn", ":saved_model_estimator", "//tensorflow:tensorflow_py_no_contrib", + "//tensorflow/python/estimator:estimator_py", ], ) @@ -40,98 +43,41 @@ py_library( srcs = ["python/estimator/baseline.py"], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow/python/estimator", "//tensorflow/python/estimator:baseline", ], ) -py_test( - name = "baseline_test", - size = "small", - srcs = ["python/estimator/baseline_test.py"], - srcs_version = "PY2AND3", - tags = [ - "no_pip", - "notsan", - ], - deps = [ - ":baseline", - ":head", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:export_export", - "//tensorflow/python/estimator:metric_keys", - "//tensorflow/python/estimator:numpy_io", - "//third_party/py/numpy", - "@six_archive//:six", - ], -) - py_library( name = "boosted_trees", srcs = ["python/estimator/boosted_trees.py"], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow/python/estimator", "//tensorflow/python/estimator:boosted_trees", ], ) -py_test( - name = "boosted_trees_test", - size = "medium", - srcs = ["python/estimator/boosted_trees_test.py"], - srcs_version = "PY2AND3", - tags = [ - "no_pip", - "notsan", - ], - deps = [ - ":boosted_trees", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:numpy_io", - "//third_party/py/numpy", - ], -) - py_library( name = "dnn", srcs = ["python/estimator/dnn.py"], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator", "//tensorflow/python/estimator:dnn", ], ) -py_test( - name = "dnn_test", - size = "medium", - srcs = ["python/estimator/dnn_test.py"], - srcs_version = "PY2AND3", - tags = [ - "no_pip", - "notsan", - "optonly", # times out http://b/79220679 - ], - deps = [ - ":dnn", - ":head", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:dnn_testing_utils", - "//tensorflow/python/estimator:export_export", - "//tensorflow/python/estimator:numpy_io", - "//tensorflow/python/estimator:prediction_keys", - "//third_party/py/numpy", - "@six_archive//:six", - ], -) - py_library( name = "dnn_with_layer_annotations", srcs = ["python/estimator/dnn_with_layer_annotations.py"], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator", "//tensorflow/python/estimator:head", @@ -140,64 +86,18 @@ py_library( ], ) -py_test( - name = "dnn_with_layer_annotations_test", - size = "medium", - srcs = ["python/estimator/dnn_with_layer_annotations_test.py"], - shard_count = 4, - srcs_version = "PY2AND3", - tags = [ - "no_pip", - "notsan", # b/67510291 - ], - deps = [ - ":dnn_with_layer_annotations", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:dnn", - "//tensorflow/python/estimator:dnn_testing_utils", - "//tensorflow/python/estimator:export_export", - "//tensorflow/python/estimator:numpy_io", - "//tensorflow/python/estimator:pandas_io", - "//tensorflow/python/estimator:prediction_keys", - "@six_archive//:six", - ], -) - py_library( name = "dnn_linear_combined", srcs = ["python/estimator/dnn_linear_combined.py"], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator", "//tensorflow/python/estimator:dnn_linear_combined", ], ) -py_test( - name = "dnn_linear_combined_test", - size = "medium", - srcs = ["python/estimator/dnn_linear_combined_test.py"], - shard_count = 3, - srcs_version = "PY2AND3", - tags = [ - "no_pip", - "notsan", - ], - deps = [ - ":dnn_linear_combined", - ":head", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:dnn_testing_utils", - "//tensorflow/python/estimator:export_export", - "//tensorflow/python/estimator:linear_testing_utils", - "//tensorflow/python/estimator:numpy_io", - "//tensorflow/python/estimator:prediction_keys", - "//third_party/py/numpy", - "@six_archive//:six", - ], -) - py_library( name = "extenders", srcs = [ @@ -205,6 +105,7 @@ py_library( ], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator", "//tensorflow/python/estimator:model_fn", @@ -213,23 +114,6 @@ py_library( ], ) -py_test( - name = "extenders_test", - size = "medium", - srcs = ["python/estimator/extenders_test.py"], - srcs_version = "PY2AND3", - tags = ["notsan"], # b/62863147 - deps = [ - ":extenders", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/contrib/data/python/ops:dataset_ops", - "//tensorflow/contrib/predictor", - "//tensorflow/python/estimator:estimator_py", - "//tensorflow/python/estimator:linear", - "//third_party/py/numpy", - ], -) - py_library( name = "export", srcs = [ @@ -237,22 +121,7 @@ py_library( ], srcs_version = "PY2AND3", deps = [ - "//tensorflow/python/estimator:model_fn", - ], -) - -py_test( - name = "export_test", - size = "medium", - srcs = ["python/estimator/export_test.py"], - srcs_version = "PY2AND3", - tags = ["notsan"], # b/62863147 - deps = [ - ":export", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator", - "//tensorflow/python/estimator:export_export", - "//tensorflow/python/estimator:export_output", + ":expect_tensorflow_estimator_installed", "//tensorflow/python/estimator:model_fn", ], ) @@ -264,24 +133,12 @@ py_library( ], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator:exporter", ], ) -py_test( - name = "exporter_test", - size = "medium", - srcs = ["python/estimator/exporter_test.py"], - srcs_version = "PY2AND3", - deps = [ - ":exporter", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator", - "//tensorflow/python/estimator:exporter", - ], -) - py_library( name = "head", srcs = [ @@ -289,6 +146,7 @@ py_library( ], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator:export_output", "//tensorflow/python/estimator:head", @@ -298,22 +156,6 @@ py_library( ], ) -py_test( - name = "head_test", - size = "medium", - srcs = ["python/estimator/head_test.py"], - srcs_version = "PY2AND3", - deps = [ - ":head", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:metric_keys", - "//tensorflow/python/estimator:model_fn", - "//tensorflow/python/estimator:prediction_keys", - "//third_party/py/numpy", - "@six_archive//:six", - ], -) - py_library( name = "hooks", srcs = [ @@ -321,58 +163,23 @@ py_library( ], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator:estimator_py", ], ) -py_test( - name = "hooks_test", - size = "medium", - srcs = ["python/estimator/hooks_test.py"], - srcs_version = "PY2AND3", - tags = ["notsan"], - deps = [ - ":hooks", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:estimator_py", - "//third_party/py/numpy", - "@six_archive//:six", - ], -) - py_library( name = "linear", srcs = ["python/estimator/linear.py"], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow/python/estimator", "//tensorflow/python/estimator:linear", ], ) -py_test( - name = "linear_test", - size = "medium", - srcs = ["python/estimator/linear_test.py"], - srcs_version = "PY2AND3", - tags = [ - "no_pip", - "notsan", - ], - deps = [ - ":head", - ":linear", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:export_export", - "//tensorflow/python/estimator:linear_testing_utils", - "//tensorflow/python/estimator:numpy_io", - "//tensorflow/python/estimator:prediction_keys", - "//third_party/py/numpy", - "@six_archive//:six", - ], -) - py_library( name = "logit_fns", srcs = [ @@ -380,24 +187,13 @@ py_library( ], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator:dnn", "//tensorflow/python/estimator:linear", ], ) -py_test( - name = "logit_fns_test", - size = "small", - srcs = ["python/estimator/logit_fns_test.py"], - srcs_version = "PY2AND3", - deps = [ - ":logit_fns", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:model_fn", - ], -) - py_library( name = "multi_head", srcs = [ @@ -405,6 +201,7 @@ py_library( ], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator:export_output", "//tensorflow/python/estimator:head", @@ -414,23 +211,6 @@ py_library( ], ) -py_test( - name = "multi_head_test", - size = "small", - srcs = ["python/estimator/multi_head_test.py"], - srcs_version = "PY2AND3", - deps = [ - ":head", - ":multi_head", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator:metric_keys", - "//tensorflow/python/estimator:model_fn", - "//tensorflow/python/estimator:prediction_keys", - "//third_party/py/numpy", - "@six_archive//:six", - ], -) - py_library( name = "replicate_model_fn", srcs = [ @@ -438,6 +218,7 @@ py_library( ], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator:export_output", "//tensorflow/python/estimator:model_fn", @@ -446,35 +227,12 @@ py_library( ], ) -cuda_py_test( - name = "replicate_model_fn_test", - size = "medium", - srcs = ["python/estimator/replicate_model_fn_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator", - "//tensorflow/python/estimator:dnn", - "//tensorflow/python/estimator:export_export", - "//tensorflow/python/estimator:export_output", - "//tensorflow/python/estimator:model_fn", - "//tensorflow/python/estimator:numpy_io", - "//tensorflow/python/estimator:optimizers", - "//tensorflow/python/estimator:prediction_keys", - ":replicate_model_fn", - ], - tags = [ - "manual", - "multi_gpu", - "notap", - ], -) - py_library( name = "rnn", srcs = ["python/estimator/rnn.py"], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", ":extenders", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/contrib/feature_column:feature_column_py", @@ -485,55 +243,22 @@ py_library( ], ) -py_test( - name = "rnn_test", - size = "medium", - srcs = ["python/estimator/rnn_test.py"], - srcs_version = "PY2AND3", - tags = [ - "no_pip", - "noasan", # times out - "notsan", - "optonly", # times out http://b/79220679 - ], - deps = [ - ":head", - ":rnn", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/contrib/data", - "//tensorflow/python/estimator:numpy_io", - "//tensorflow/python/estimator:parsing_utils", - "//third_party/py/numpy", - "@six_archive//:six", - ], -) - py_library( name = "early_stopping", srcs = ["python/estimator/early_stopping.py"], srcs_version = "PY2AND3", deps = [ + ":expect_tensorflow_estimator_installed", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator", ], ) -py_test( - name = "early_stopping_test", - srcs = ["python/estimator/early_stopping_test.py"], - srcs_version = "PY2AND3", - deps = [ - ":early_stopping", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator", - "@absl_py//absl/testing:parameterized", - ], -) - py_library( name = "saved_model_estimator", srcs = ["python/estimator/saved_model_estimator.py"], deps = [ + ":expect_tensorflow_estimator_installed", ":export", "//tensorflow:tensorflow_py_no_contrib", "//tensorflow/python/estimator", @@ -542,21 +267,9 @@ py_library( ], ) -py_test( - name = "saved_model_estimator_test", - size = "medium", - srcs = ["python/estimator/saved_model_estimator_test.py"], - srcs_version = "PY2AND3", - tags = [ - "notsan", - ], - deps = [ - ":export", - ":saved_model_estimator", - "//tensorflow:tensorflow_py_no_contrib", - "//tensorflow/python/estimator", - "//tensorflow/python/estimator:export_export", - "//tensorflow/python/estimator:export_output", - "//tensorflow/python/estimator:model_fn", - ], +py_library( + name = "expect_tensorflow_estimator_installed", + # This is a dummy rule used as a dependency in open-source. + # We expect tensorflow_estimator to already be installed. + visibility = ["//visibility:public"], ) diff --git a/tensorflow/contrib/estimator/__init__.py b/tensorflow/contrib/estimator/__init__.py index 419609b1af..d9457df4aa 100644 --- a/tensorflow/contrib/estimator/__init__.py +++ b/tensorflow/contrib/estimator/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# 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. @@ -12,33 +12,38 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Experimental utilities re:tf.estimator.*.""" +"""estimator python module. + +Importing from tensorflow.python.estimator +is unsupported and will soon break! +""" + +# pylint: disable=unused-import,g-bad-import-order,g-import-not-at-top,wildcard-import from __future__ import absolute_import from __future__ import division from __future__ import print_function -# pylint: disable=unused-import,line-too-long,wildcard-import -from tensorflow.contrib.estimator.python.estimator.baseline import * -from tensorflow.contrib.estimator.python.estimator.boosted_trees import * -from tensorflow.contrib.estimator.python.estimator.dnn import * -from tensorflow.contrib.estimator.python.estimator.dnn_with_layer_annotations import * -from tensorflow.contrib.estimator.python.estimator.dnn_linear_combined import * -from tensorflow.contrib.estimator.python.estimator.early_stopping import * -from tensorflow.contrib.estimator.python.estimator.export import * -from tensorflow.contrib.estimator.python.estimator.extenders import * -from tensorflow.contrib.estimator.python.estimator.head import * -from tensorflow.contrib.estimator.python.estimator.hooks import * -from tensorflow.contrib.estimator.python.estimator.linear import * -from tensorflow.contrib.estimator.python.estimator.logit_fns import * -from tensorflow.contrib.estimator.python.estimator.multi_head import * -from tensorflow.contrib.estimator.python.estimator.replicate_model_fn import * -from tensorflow.contrib.estimator.python.estimator.rnn import * -from tensorflow.contrib.estimator.python.estimator.saved_model_estimator import * -from tensorflow.python.estimator.export.export import * +# Importing from tensorflow.python.estimator +# is unsupported and will soon break! + +from tensorflow_estimator.contrib import estimator + +# Fixes remove_undocumented not working as intended. +# +# Problem is that when the below import happens (for first time, +# Python only imports things once), Python sets attribute named +# 'python' to this package. If this first import happens +# after the call to remove_undocumented, then the 'python' +# attribute won't be removed. +import tensorflow.contrib.estimator.python + +# Include attrs that start with single underscore. +_HAS_DYNAMIC_ATTRIBUTES = True +estimator.__all__ = [s for s in dir(estimator) if not s.startswith('__')] +from tensorflow_estimator.contrib.estimator import * from tensorflow.python.util.all_util import remove_undocumented -# pylint: enable=unused-import,line-too-long,wildcard-import _allowed_symbols = [ 'add_metrics', diff --git a/tensorflow/contrib/estimator/python/estimator/baseline.py b/tensorflow/contrib/estimator/python/estimator/baseline.py index beffbee730..fcd3200915 100644 --- a/tensorflow/contrib/estimator/python/estimator/baseline.py +++ b/tensorflow/contrib/estimator/python/estimator/baseline.py @@ -1,4 +1,4 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# 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. @@ -12,87 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Baseline estimators.""" +"""baseline python module. + +Importing from tensorflow.python.estimator is unsupported +and will soon break! +""" +# pylint: disable=unused-import,g-bad-import-order,g-import-not-at-top,wildcard-import + from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.estimator import estimator -from tensorflow.python.estimator.canned import baseline - - -class BaselineEstimator(estimator.Estimator): - """An estimator that can establish a simple baseline. - - The estimator uses a user-specified head. - - This estimator ignores feature values and will learn to predict the average - value of each label. E.g. for single-label classification problems, this will - predict the probability distribution of the classes as seen in the labels. - For multi-label classification problems, it will predict the ratio of examples - that contain each class. - - Example: - - ```python - - # Build baseline multi-label classifier. - estimator = BaselineEstimator( - head=tf.contrib.estimator.multi_label_head(n_classes=3)) - - # Input builders - def input_fn_train: # returns x, y (where y represents label's class index). - pass - - def input_fn_eval: # returns x, y (where y represents label's class index). - pass - - # Fit model. - estimator.train(input_fn=input_fn_train) - - # Evaluates cross entropy between the test and train labels. - loss = classifier.evaluate(input_fn=input_fn_eval)["loss"] - - # For each class, predicts the ratio of training examples that contain the - # class. - predictions = classifier.predict(new_samples) - - ``` - - Input of `train` and `evaluate` should have following features, - otherwise there will be a `KeyError`: - - * if `weight_column` passed to the `head` constructor is not `None`, a feature - with `key=weight_column` whose value is a `Tensor`. - """ +from tensorflow_estimator.contrib.estimator.python.estimator import baseline - def __init__(self, - head, - model_dir=None, - optimizer='Ftrl', - config=None): - """Initializes a BaselineEstimator instance. +# Include attrs that start with single underscore. +_HAS_DYNAMIC_ATTRIBUTES = True +baseline.__all__ = [s for s in dir(baseline) if not s.startswith('__')] - Args: - head: A `_Head` instance constructed with a method such as - `tf.contrib.estimator.multi_label_head`. - model_dir: Directory to save model parameters, graph and etc. This can - also be used to load checkpoints from the directory into a estimator to - continue training a previously saved model. - optimizer: String, `tf.Optimizer` object, or callable that creates the - optimizer to use for training. If not specified, will use - `FtrlOptimizer` with a default learning rate of 0.3. - config: `RunConfig` object to configure the runtime settings. - """ - def _model_fn(features, labels, mode, config): - return baseline._baseline_model_fn( # pylint: disable=protected-access - features=features, - labels=labels, - mode=mode, - head=head, - optimizer=optimizer, - config=config) - super(BaselineEstimator, self).__init__( - model_fn=_model_fn, - model_dir=model_dir, - config=config) +from tensorflow_estimator.contrib.estimator.python.estimator.baseline import * diff --git a/tensorflow/contrib/estimator/python/estimator/baseline_test.py b/tensorflow/contrib/estimator/python/estimator/baseline_test.py deleted file mode 100644 index 513feb03b6..0000000000 --- a/tensorflow/contrib/estimator/python/estimator/baseline_test.py +++ /dev/null @@ -1,436 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for baseline.py.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import shutil -import tempfile - -import numpy as np -import six - -from tensorflow.contrib.estimator.python.estimator import baseline -from tensorflow.contrib.estimator.python.estimator import head as head_lib -from tensorflow.python.client import session as tf_session -from tensorflow.python.estimator.canned import metric_keys -from tensorflow.python.estimator.export import export -from tensorflow.python.estimator.inputs import numpy_io -from tensorflow.python.feature_column import feature_column as feature_column_lib -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops -from tensorflow.python.ops import check_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.ops import variables -from tensorflow.python.ops.losses import losses -from tensorflow.python.platform import gfile -from tensorflow.python.platform import test -from tensorflow.python.summary.writer import writer_cache -from tensorflow.python.training import checkpoint_utils -from tensorflow.python.training import optimizer -from tensorflow.python.training import saver - -# Names of variables created by model. -BIAS_NAME = 'baseline/bias' - - -def assert_close(expected, actual, rtol=1e-04, name='assert_close'): - with ops.name_scope(name, 'assert_close', (expected, actual, rtol)) as scope: - expected = ops.convert_to_tensor(expected, name='expected') - actual = ops.convert_to_tensor(actual, name='actual') - rdiff = math_ops.abs(expected - actual, 'diff') / math_ops.abs(expected) - rtol = ops.convert_to_tensor(rtol, name='rtol') - return check_ops.assert_less( - rdiff, - rtol, - data=('Condition expected =~ actual did not hold element-wise:' - 'expected = ', expected, 'actual = ', actual, 'rdiff = ', rdiff, - 'rtol = ', rtol,), - name=scope) - - -def save_variables_to_ckpt(model_dir): - init_all_op = [variables.global_variables_initializer()] - with tf_session.Session() as sess: - sess.run(init_all_op) - saver.Saver().save(sess, os.path.join(model_dir, 'model.ckpt')) - - -def _baseline_estimator_fn( - weight_column=None, label_dimension=1, *args, **kwargs): - """Returns a BaselineEstimator that uses regression_head.""" - return baseline.BaselineEstimator( - head=head_lib.regression_head( - weight_column=weight_column, label_dimension=label_dimension, - # Tests in core (from which this test inherits) test the sum loss. - loss_reduction=losses.Reduction.SUM), - *args, **kwargs) - - -class BaselineEstimatorEvaluationTest(test.TestCase): - - def setUp(self): - self._model_dir = tempfile.mkdtemp() - - def tearDown(self): - if self._model_dir: - writer_cache.FileWriterCache.clear() - shutil.rmtree(self._model_dir) - - def test_evaluation_batch(self): - """Tests evaluation for batch_size==2.""" - with ops.Graph().as_default(): - variables.Variable([13.0], name=BIAS_NAME) - variables.Variable( - 100, name=ops.GraphKeys.GLOBAL_STEP, dtype=dtypes.int64) - save_variables_to_ckpt(self._model_dir) - - baseline_estimator = _baseline_estimator_fn(model_dir=self._model_dir) - eval_metrics = baseline_estimator.evaluate( - input_fn=lambda: ({'age': ((1,), (1,))}, ((10.,), (10.,))), steps=1) - - # Logit is bias = 13, while label is 10. - # Loss per example is 3**2 = 9. - # Training loss is the sum over batch = 9 + 9 = 18 - # Average loss is the average over batch = 9 - self.assertDictEqual({ - metric_keys.MetricKeys.LOSS: 18., - metric_keys.MetricKeys.LOSS_MEAN: 9., - metric_keys.MetricKeys.PREDICTION_MEAN: 13., - metric_keys.MetricKeys.LABEL_MEAN: 10., - ops.GraphKeys.GLOBAL_STEP: 100 - }, eval_metrics) - - def test_evaluation_weights(self): - """Tests evaluation with weights.""" - with ops.Graph().as_default(): - variables.Variable([13.0], name=BIAS_NAME) - variables.Variable( - 100, name=ops.GraphKeys.GLOBAL_STEP, dtype=dtypes.int64) - save_variables_to_ckpt(self._model_dir) - - def _input_fn(): - features = {'age': ((1,), (1,)), 'weights': ((1.,), (2.,))} - labels = ((10.,), (10.,)) - return features, labels - - baseline_estimator = _baseline_estimator_fn( - weight_column='weights', - model_dir=self._model_dir) - eval_metrics = baseline_estimator.evaluate(input_fn=_input_fn, steps=1) - - # Logit is bias = 13, while label is 10. - # Loss per example is 3**2 = 9. - # Training loss is the weighted sum over batch = 9 + 2*9 = 27 - # average loss is the weighted average = 9 + 2*9 / (1 + 2) = 9 - self.assertDictEqual({ - metric_keys.MetricKeys.LOSS: 27., - metric_keys.MetricKeys.LOSS_MEAN: 9., - metric_keys.MetricKeys.PREDICTION_MEAN: 13., - metric_keys.MetricKeys.LABEL_MEAN: 10., - ops.GraphKeys.GLOBAL_STEP: 100 - }, eval_metrics) - - def test_evaluation_for_multi_dimensions(self): - label_dim = 2 - with ops.Graph().as_default(): - variables.Variable([46.0, 58.0], name=BIAS_NAME) - variables.Variable(100, name='global_step', dtype=dtypes.int64) - save_variables_to_ckpt(self._model_dir) - - baseline_estimator = _baseline_estimator_fn( - label_dimension=label_dim, - model_dir=self._model_dir) - input_fn = numpy_io.numpy_input_fn( - x={ - 'age': np.array([[2., 4., 5.]]), - }, - y=np.array([[46., 58.]]), - batch_size=1, - num_epochs=None, - shuffle=False) - eval_metrics = baseline_estimator.evaluate(input_fn=input_fn, steps=1) - - self.assertItemsEqual( - (metric_keys.MetricKeys.LOSS, metric_keys.MetricKeys.LOSS_MEAN, - metric_keys.MetricKeys.PREDICTION_MEAN, - metric_keys.MetricKeys.LABEL_MEAN, ops.GraphKeys.GLOBAL_STEP), - eval_metrics.keys()) - - # Logit is bias which is [46, 58] - self.assertAlmostEqual(0, eval_metrics[metric_keys.MetricKeys.LOSS]) - - -class BaselineEstimatorPredictTest(test.TestCase): - - def setUp(self): - self._model_dir = tempfile.mkdtemp() - - def tearDown(self): - if self._model_dir: - writer_cache.FileWriterCache.clear() - shutil.rmtree(self._model_dir) - - def test_1d(self): - """Tests predict when all variables are one-dimensional.""" - with ops.Graph().as_default(): - variables.Variable([.2], name=BIAS_NAME) - variables.Variable(100, name='global_step', dtype=dtypes.int64) - save_variables_to_ckpt(self._model_dir) - - baseline_estimator = _baseline_estimator_fn(model_dir=self._model_dir) - - predict_input_fn = numpy_io.numpy_input_fn( - x={'x': np.array([[2.]])}, - y=None, - batch_size=1, - num_epochs=1, - shuffle=False) - predictions = baseline_estimator.predict(input_fn=predict_input_fn) - predicted_scores = list([x['predictions'] for x in predictions]) - # x * weight + bias = 2. * 10. + .2 = 20.2 - self.assertAllClose([[.2]], predicted_scores) - - def testMultiDim(self): - """Tests predict when all variables are multi-dimenstional.""" - batch_size = 2 - label_dimension = 3 - with ops.Graph().as_default(): - variables.Variable( # shape=[label_dimension] - [.2, .4, .6], name=BIAS_NAME) - variables.Variable(100, name='global_step', dtype=dtypes.int64) - save_variables_to_ckpt(self._model_dir) - - baseline_estimator = _baseline_estimator_fn( - label_dimension=label_dimension, - model_dir=self._model_dir) - - predict_input_fn = numpy_io.numpy_input_fn( - # x shape=[batch_size, x_dim] - x={'x': np.array([[1., 2., 3., 4.], [5., 6., 7., 8.]])}, - y=None, - batch_size=batch_size, - num_epochs=1, - shuffle=False) - predictions = baseline_estimator.predict(input_fn=predict_input_fn) - predicted_scores = list([x['predictions'] for x in predictions]) - # score = bias, shape=[batch_size, label_dimension] - self.assertAllClose([[0.2, 0.4, 0.6], [0.2, 0.4, 0.6]], - predicted_scores) - - -class BaselineEstimatorIntegrationTest(test.TestCase): - - def setUp(self): - self._model_dir = tempfile.mkdtemp() - - def tearDown(self): - if self._model_dir: - writer_cache.FileWriterCache.clear() - shutil.rmtree(self._model_dir) - - def _test_complete_flow(self, train_input_fn, eval_input_fn, predict_input_fn, - input_dimension, label_dimension, prediction_length): - feature_columns = [ - feature_column_lib.numeric_column('x', shape=(input_dimension,)) - ] - est = _baseline_estimator_fn( - label_dimension=label_dimension, - model_dir=self._model_dir) - - # TRAIN - # learn y = x - est.train(train_input_fn, steps=200) - - # EVALUTE - scores = est.evaluate(eval_input_fn) - self.assertEqual(200, scores[ops.GraphKeys.GLOBAL_STEP]) - self.assertIn(metric_keys.MetricKeys.LOSS, six.iterkeys(scores)) - - # PREDICT - predictions = np.array( - [x['predictions'] for x in est.predict(predict_input_fn)]) - self.assertAllEqual((prediction_length, label_dimension), predictions.shape) - - # EXPORT - feature_spec = feature_column_lib.make_parse_example_spec(feature_columns) - serving_input_receiver_fn = export.build_parsing_serving_input_receiver_fn( - feature_spec) - export_dir = est.export_savedmodel(tempfile.mkdtemp(), - serving_input_receiver_fn) - self.assertTrue(gfile.Exists(export_dir)) - - def test_numpy_input_fn(self): - """Tests complete flow with numpy_input_fn.""" - label_dimension = 2 - input_dimension = label_dimension - batch_size = 10 - prediction_length = batch_size - data = np.linspace(0., 2., batch_size * label_dimension, dtype=np.float32) - data = data.reshape(batch_size, label_dimension) - - train_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - y=data, - batch_size=batch_size, - num_epochs=None, - shuffle=True) - eval_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - y=data, - batch_size=batch_size, - num_epochs=1, - shuffle=False) - predict_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - y=None, - batch_size=batch_size, - num_epochs=1, - shuffle=False) - - self._test_complete_flow( - train_input_fn=train_input_fn, - eval_input_fn=eval_input_fn, - predict_input_fn=predict_input_fn, - input_dimension=input_dimension, - label_dimension=label_dimension, - prediction_length=prediction_length) - - -class BaselineEstimatorTrainingTest(test.TestCase): - - def setUp(self): - self._model_dir = tempfile.mkdtemp() - - def tearDown(self): - if self._model_dir: - writer_cache.FileWriterCache.clear() - shutil.rmtree(self._model_dir) - - def _mock_optimizer(self, expected_loss=None): - expected_var_names = [ - '%s:0' % BIAS_NAME - ] - - def _minimize(loss, global_step=None, var_list=None): - trainable_vars = var_list or ops.get_collection( - ops.GraphKeys.TRAINABLE_VARIABLES) - self.assertItemsEqual(expected_var_names, - [var.name for var in trainable_vars]) - - # Verify loss. We can't check the value directly, so we add an assert op. - self.assertEquals(0, loss.shape.ndims) - if expected_loss is None: - if global_step is not None: - return state_ops.assign_add(global_step, 1).op - return control_flow_ops.no_op() - assert_loss = assert_close( - math_ops.to_float(expected_loss, name='expected'), - loss, - name='assert_loss') - with ops.control_dependencies((assert_loss,)): - if global_step is not None: - return state_ops.assign_add(global_step, 1).op - return control_flow_ops.no_op() - - mock_optimizer = test.mock.NonCallableMock( - spec=optimizer.Optimizer, - wraps=optimizer.Optimizer(use_locking=False, name='my_optimizer')) - mock_optimizer.minimize = test.mock.MagicMock(wraps=_minimize) - - # NOTE: Estimator.params performs a deepcopy, which wreaks havoc with mocks. - # So, return mock_optimizer itself for deepcopy. - mock_optimizer.__deepcopy__ = lambda _: mock_optimizer - return mock_optimizer - - def _assert_checkpoint(self, - label_dimension, - expected_global_step, - expected_bias=None): - shapes = { - name: shape - for (name, shape) in checkpoint_utils.list_variables(self._model_dir) - } - - self.assertEqual([], shapes[ops.GraphKeys.GLOBAL_STEP]) - self.assertEqual(expected_global_step, - checkpoint_utils.load_variable(self._model_dir, - ops.GraphKeys.GLOBAL_STEP)) - - self.assertEqual([label_dimension], shapes[BIAS_NAME]) - if expected_bias is not None: - self.assertEqual(expected_bias, - checkpoint_utils.load_variable(self._model_dir, - BIAS_NAME)) - - def testFromScratch(self): - # Create BaselineRegressor. - label = 5. - age = 17 - # loss = (logits - label)^2 = (0 - 5.)^2 = 25. - mock_optimizer = self._mock_optimizer(expected_loss=25.) - baseline_estimator = _baseline_estimator_fn( - model_dir=self._model_dir, - optimizer=mock_optimizer) - self.assertEqual(0, mock_optimizer.minimize.call_count) - - # Train for a few steps, and validate optimizer and final checkpoint. - num_steps = 10 - baseline_estimator.train( - input_fn=lambda: ({'age': ((age,),)}, ((label,),)), steps=num_steps) - self.assertEqual(1, mock_optimizer.minimize.call_count) - self._assert_checkpoint( - label_dimension=1, - expected_global_step=num_steps, - expected_bias=[0.]) - - def testFromCheckpoint(self): - # Create initial checkpoint. - bias = 7.0 - initial_global_step = 100 - with ops.Graph().as_default(): - variables.Variable([bias], name=BIAS_NAME) - variables.Variable( - initial_global_step, - name=ops.GraphKeys.GLOBAL_STEP, - dtype=dtypes.int64) - save_variables_to_ckpt(self._model_dir) - - # logits = bias = 6. - # loss = (logits - label)^2 = (7 - 5)^2 = 4 - mock_optimizer = self._mock_optimizer(expected_loss=4.) - baseline_estimator = _baseline_estimator_fn( - model_dir=self._model_dir, - optimizer=mock_optimizer) - self.assertEqual(0, mock_optimizer.minimize.call_count) - - # Train for a few steps, and validate optimizer and final checkpoint. - num_steps = 10 - baseline_estimator.train( - input_fn=lambda: ({'age': ((17,),)}, ((5.,),)), steps=num_steps) - self.assertEqual(1, mock_optimizer.minimize.call_count) - self._assert_checkpoint( - label_dimension=1, - expected_global_step=initial_global_step + num_steps, - expected_bias=[bias]) - - -if __name__ == '__main__': - test.main() diff --git a/tensorflow/contrib/estimator/python/estimator/boosted_trees.py b/tensorflow/contrib/estimator/python/estimator/boosted_trees.py index b131ed4f12..4cb66883a5 100644 --- a/tensorflow/contrib/estimator/python/estimator/boosted_trees.py +++ b/tensorflow/contrib/estimator/python/estimator/boosted_trees.py @@ -12,414 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Boosted Trees estimators.""" +"""boosted_trees python module. + +Importing from tensorflow.python.estimator is unsupported +and will soon break! +""" +# pylint: disable=unused-import,g-bad-import-order,g-import-not-at-top,wildcard-import + from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.data.ops import dataset_ops -from tensorflow.python.estimator import estimator -from tensorflow.python.estimator.canned import boosted_trees as canned_boosted_trees -from tensorflow.python.estimator.canned import head as head_lib - - -def _validate_input_fn_and_repeat_dataset(train_input_fn): - """Validates whether the input_fn is valid, and repeat() if tf.Dataset.""" - def _input_fn(): - result_input_fn = train_input_fn() - if isinstance(result_input_fn, dataset_ops.Dataset): - return result_input_fn.repeat() - return result_input_fn - - return _input_fn - - -def _is_classification_head(head): - """Infers if the head is a classification head.""" - # Check using all classification heads defined in canned/head.py. However, it - # is not a complete list - it does not check for other classification heads - # not defined in the head library. - # pylint: disable=protected-access - return isinstance(head, - (head_lib._BinaryLogisticHeadWithSigmoidCrossEntropyLoss, - head_lib._MultiClassHeadWithSoftmaxCrossEntropyLoss)) - # pylint: enable=protected-access - - -class _BoostedTreesEstimator(canned_boosted_trees._BoostedTreesBase): # pylint: disable=protected-access - """An Estimator for Tensorflow Boosted Trees models.""" - - def __init__(self, - feature_columns, - n_batches_per_layer, - head, - model_dir=None, - weight_column=None, - n_trees=100, - max_depth=6, - learning_rate=0.1, - l1_regularization=0., - l2_regularization=0., - tree_complexity=0., - min_node_weight=0., - config=None, - center_bias=False, - pruning_mode='none'): - """Initializes a `BoostedTreesEstimator` instance. - - Args: - feature_columns: An iterable containing all the feature columns used by - the model. All items in the set should be instances of classes derived - from `FeatureColumn`. - n_batches_per_layer: the number of batches to collect statistics per - layer. - head: the `Head` instance defined for Estimator. - model_dir: Directory to save model parameters, graph and etc. This can - also be used to load checkpoints from the directory into an estimator - to continue training a previously saved model. - weight_column: A string or a `_NumericColumn` created by - `tf.feature_column.numeric_column` defining feature column representing - weights. It is used to downweight or boost examples during training. It - will be multiplied by the loss of the example. If it is a string, it is - used as a key to fetch weight tensor from the `features`. If it is a - `_NumericColumn`, raw tensor is fetched by key `weight_column.key`, - then weight_column.normalizer_fn is applied on it to get weight tensor. - n_trees: number trees to be created. - max_depth: maximum depth of the tree to grow. - learning_rate: shrinkage parameter to be used when a tree added to the - model. - l1_regularization: regularization multiplier applied to the absolute - weights of the tree leafs. - l2_regularization: regularization multiplier applied to the square weights - of the tree leafs. - tree_complexity: regularization factor to penalize trees with more leaves. - min_node_weight: minimum hessian a node must have for a split to be - considered. The value will be compared with sum(leaf_hessian)/ - (batch_size * n_batches_per_layer). - config: `RunConfig` object to configure the runtime settings. - center_bias: Whether bias centering needs to occur. Bias centering refers - to the first node in the very first tree returning the prediction that - is aligned with the original labels distribution. For example, for - regression problems, the first node will return the mean of the labels. - For binary classification problems, it will return a logit for a prior - probability of label 1. - pruning_mode: one of 'none', 'pre', 'post' to indicate no pruning, pre- - pruning (do not split a node if not enough gain is observed) and post - pruning (build the tree up to a max depth and then prune branches with - negative gain). For pre and post pruning, you MUST provide - tree_complexity >0. - - Raises: - ValueError: when wrong arguments are given or unsupported functionalities - are requested. - """ - # HParams for the model. - # pylint: disable=protected-access - tree_hparams = canned_boosted_trees._TreeHParams( - n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, - tree_complexity, min_node_weight, center_bias, pruning_mode) - - def _model_fn(features, labels, mode, config): - return canned_boosted_trees._bt_model_fn( - features, - labels, - mode, - head, - feature_columns, - tree_hparams, - n_batches_per_layer, - config=config) - - super(_BoostedTreesEstimator, self).__init__( - model_fn=_model_fn, - model_dir=model_dir, - config=config, - feature_columns=feature_columns, - head=head, - center_bias=center_bias, - is_classification=_is_classification_head(head)) - # pylint: enable=protected-access - - -def boosted_trees_classifier_train_in_memory( - train_input_fn, - feature_columns, - model_dir=None, - n_classes=canned_boosted_trees._HOLD_FOR_MULTI_CLASS_SUPPORT, - weight_column=None, - label_vocabulary=None, - n_trees=100, - max_depth=6, - learning_rate=0.1, - l1_regularization=0., - l2_regularization=0., - tree_complexity=0., - min_node_weight=0., - config=None, - train_hooks=None, - center_bias=False, - pruning_mode='none'): - """Trains a boosted tree classifier with in memory dataset. - - Example: - - ```python - bucketized_feature_1 = bucketized_column( - numeric_column('feature_1'), BUCKET_BOUNDARIES_1) - bucketized_feature_2 = bucketized_column( - numeric_column('feature_2'), BUCKET_BOUNDARIES_2) - - def train_input_fn(): - dataset = create-dataset-from-training-data - # This is tf.data.Dataset of a tuple of feature dict and label. - # e.g. Dataset.zip((Dataset.from_tensors({'f1': f1_array, ...}), - # Dataset.from_tensors(label_array))) - # The returned Dataset shouldn't be batched. - # If Dataset repeats, only the first repetition would be used for training. - return dataset - - classifier = boosted_trees_classifier_train_in_memory( - train_input_fn, - feature_columns=[bucketized_feature_1, bucketized_feature_2], - n_trees=100, - ... - ) - - def input_fn_eval(): - ... - return dataset - - metrics = classifier.evaluate(input_fn=input_fn_eval, steps=10) - ``` - - Args: - train_input_fn: the input function returns a dataset containing a single - epoch of *unbatched* features and labels. - feature_columns: An iterable containing all the feature columns used by - the model. All items in the set should be instances of classes derived - from `FeatureColumn`. - model_dir: Directory to save model parameters, graph and etc. This can - also be used to load checkpoints from the directory into an estimator - to continue training a previously saved model. - n_classes: number of label classes. Default is binary classification. - Multiclass support is not yet implemented. - weight_column: A string or a `_NumericColumn` created by - `tf.feature_column.numeric_column` defining feature column representing - weights. It is used to downweight or boost examples during training. It - will be multiplied by the loss of the example. If it is a string, it is - used as a key to fetch weight tensor from the `features`. If it is a - `_NumericColumn`, raw tensor is fetched by key `weight_column.key`, - then weight_column.normalizer_fn is applied on it to get weight tensor. - label_vocabulary: A list of strings represents possible label values. If - given, labels must be string type and have any value in - `label_vocabulary`. If it is not given, that means labels are - already encoded as integer or float within [0, 1] for `n_classes=2` and - encoded as integer values in {0, 1,..., n_classes-1} for `n_classes`>2 . - Also there will be errors if vocabulary is not provided and labels are - string. - n_trees: number trees to be created. - max_depth: maximum depth of the tree to grow. - learning_rate: shrinkage parameter to be used when a tree added to the - model. - l1_regularization: regularization multiplier applied to the absolute - weights of the tree leafs. - l2_regularization: regularization multiplier applied to the square weights - of the tree leafs. - tree_complexity: regularization factor to penalize trees with more leaves. - min_node_weight: minimum hessian a node must have for a split to be - considered. The value will be compared with sum(leaf_hessian)/ - (batch_size * n_batches_per_layer). - config: `RunConfig` object to configure the runtime settings. - train_hooks: a list of Hook instances to be passed to estimator.train() - center_bias: Whether bias centering needs to occur. Bias centering refers - to the first node in the very first tree returning the prediction that - is aligned with the original labels distribution. For example, for - regression problems, the first node will return the mean of the labels. - For binary classification problems, it will return a logit for a prior - probability of label 1. - pruning_mode: one of 'none', 'pre', 'post' to indicate no pruning, pre- - pruning (do not split a node if not enough gain is observed) and post - pruning (build the tree up to a max depth and then prune branches with - negative gain). For pre and post pruning, you MUST provide - tree_complexity >0. - - Returns: - a `BoostedTreesClassifier` instance created with the given arguments and - trained with the data loaded up on memory from the input_fn. - - Raises: - ValueError: when wrong arguments are given or unsupported functionalities - are requested. - """ - # pylint: disable=protected-access - # TODO(nponomareva): Support multi-class cases. - if n_classes == canned_boosted_trees._HOLD_FOR_MULTI_CLASS_SUPPORT: - n_classes = 2 - head, closed_form = ( - canned_boosted_trees._create_classification_head_and_closed_form( - n_classes, weight_column, label_vocabulary=label_vocabulary)) - - # HParams for the model. - tree_hparams = canned_boosted_trees._TreeHParams( - n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, - tree_complexity, min_node_weight, center_bias, pruning_mode) - - def _model_fn(features, labels, mode, config): - return canned_boosted_trees._bt_model_fn( - features, - labels, - mode, - head, - feature_columns, - tree_hparams, - n_batches_per_layer=1, - config=config, - closed_form_grad_and_hess_fn=closed_form, - train_in_memory=True) - - in_memory_classifier = estimator.Estimator( - model_fn=_model_fn, model_dir=model_dir, config=config) - - in_memory_classifier.train( - input_fn=_validate_input_fn_and_repeat_dataset(train_input_fn), - hooks=train_hooks) - - return in_memory_classifier - # pylint: enable=protected-access - - -def boosted_trees_regressor_train_in_memory( - train_input_fn, - feature_columns, - model_dir=None, - label_dimension=canned_boosted_trees._HOLD_FOR_MULTI_DIM_SUPPORT, - weight_column=None, - n_trees=100, - max_depth=6, - learning_rate=0.1, - l1_regularization=0., - l2_regularization=0., - tree_complexity=0., - min_node_weight=0., - config=None, - train_hooks=None, - center_bias=False, - pruning_mode='none'): - """Trains a boosted tree regressor with in memory dataset. - - Example: - - ```python - bucketized_feature_1 = bucketized_column( - numeric_column('feature_1'), BUCKET_BOUNDARIES_1) - bucketized_feature_2 = bucketized_column( - numeric_column('feature_2'), BUCKET_BOUNDARIES_2) - - def train_input_fn(): - dataset = create-dataset-from-training-data - # This is tf.data.Dataset of a tuple of feature dict and label. - # e.g. Dataset.zip((Dataset.from_tensors({'f1': f1_array, ...}), - # Dataset.from_tensors(label_array))) - # The returned Dataset shouldn't be batched. - # If Dataset repeats, only the first repetition would be used for training. - return dataset - - regressor = boosted_trees_regressor_train_in_memory( - train_input_fn, - feature_columns=[bucketized_feature_1, bucketized_feature_2], - n_trees=100, - ... - ) - - def input_fn_eval(): - ... - return dataset - - metrics = regressor.evaluate(input_fn=input_fn_eval, steps=10) - ``` - - Args: - train_input_fn: the input function returns a dataset containing a single - epoch of *unbatched* features and labels. - feature_columns: An iterable containing all the feature columns used by - the model. All items in the set should be instances of classes derived - from `FeatureColumn`. - model_dir: Directory to save model parameters, graph and etc. This can - also be used to load checkpoints from the directory into an estimator - to continue training a previously saved model. - label_dimension: Number of regression targets per example. - Multi-dimensional support is not yet implemented. - weight_column: A string or a `_NumericColumn` created by - `tf.feature_column.numeric_column` defining feature column representing - weights. It is used to downweight or boost examples during training. It - will be multiplied by the loss of the example. If it is a string, it is - used as a key to fetch weight tensor from the `features`. If it is a - `_NumericColumn`, raw tensor is fetched by key `weight_column.key`, - then weight_column.normalizer_fn is applied on it to get weight tensor. - n_trees: number trees to be created. - max_depth: maximum depth of the tree to grow. - learning_rate: shrinkage parameter to be used when a tree added to the - model. - l1_regularization: regularization multiplier applied to the absolute - weights of the tree leafs. - l2_regularization: regularization multiplier applied to the square weights - of the tree leafs. - tree_complexity: regularization factor to penalize trees with more leaves. - min_node_weight: minimum hessian a node must have for a split to be - considered. The value will be compared with sum(leaf_hessian)/ - (batch_size * n_batches_per_layer). - config: `RunConfig` object to configure the runtime settings. - train_hooks: a list of Hook instances to be passed to estimator.train(). - center_bias: Whether bias centering needs to occur. Bias centering refers - to the first node in the very first tree returning the prediction that - is aligned with the original labels distribution. For example, for - regression problems, the first node will return the mean of the labels. - For binary classification problems, it will return a logit for a prior - probability of label 1. - pruning_mode: one of 'none', 'pre', 'post' to indicate no pruning, pre- - pruning (do not split a node if not enough gain is observed) and post - pruning (build the tree up to a max depth and then prune branches with - negative gain). For pre and post pruning, you MUST provide - tree_complexity >0. - - Returns: - a `BoostedTreesClassifier` instance created with the given arguments and - trained with the data loaded up on memory from the input_fn. - - Raises: - ValueError: when wrong arguments are given or unsupported functionalities - are requested. - """ - # pylint: disable=protected-access - # TODO(nponomareva): Extend it to multi-dimension cases. - if label_dimension == canned_boosted_trees._HOLD_FOR_MULTI_DIM_SUPPORT: - label_dimension = 1 - head = canned_boosted_trees._create_regression_head(label_dimension, - weight_column) - - # HParams for the model. - tree_hparams = canned_boosted_trees._TreeHParams( - n_trees, max_depth, learning_rate, l1_regularization, l2_regularization, - tree_complexity, min_node_weight, center_bias, pruning_mode) - - def _model_fn(features, labels, mode, config): - return canned_boosted_trees._bt_model_fn( - features, - labels, - mode, - head, - feature_columns, - tree_hparams, - n_batches_per_layer=1, - config=config, - train_in_memory=True) - - in_memory_regressor = estimator.Estimator( - model_fn=_model_fn, model_dir=model_dir, config=config) +from tensorflow_estimator.contrib.estimator.python.estimator import boosted_trees - in_memory_regressor.train( - input_fn=_validate_input_fn_and_repeat_dataset(train_input_fn), - hooks=train_hooks) +# Include attrs that start with single underscore. +_HAS_DYNAMIC_ATTRIBUTES = True +boosted_trees.__all__ = [ + s for s in dir(boosted_trees) if not s.startswith('__') +] - return in_memory_regressor - # pylint: enable=protected-access +from tensorflow_estimator.contrib.estimator.python.estimator.boosted_trees import * diff --git a/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py b/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py deleted file mode 100644 index e23d9c0fc4..0000000000 --- a/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py +++ /dev/null @@ -1,438 +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. -# ============================================================================== -"""Tests boosted_trees estimators.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np - -from tensorflow.contrib.estimator.python.estimator import boosted_trees -from tensorflow.core.kernels.boosted_trees import boosted_trees_pb2 -from tensorflow.python.data.ops import dataset_ops -from tensorflow.python.estimator.canned import boosted_trees as canned_boosted_trees -from tensorflow.python.estimator.inputs import numpy_io -from tensorflow.python.feature_column import feature_column -from tensorflow.python.framework import dtypes -from tensorflow.python.framework import ops -from tensorflow.python.framework import test_util -from tensorflow.python.platform import googletest -from tensorflow.python.training import checkpoint_utils - -NUM_FEATURES = 3 - -BUCKET_BOUNDARIES = [-2., .5, 12.] # Boundaries for all the features. -INPUT_FEATURES = np.array( - [ - [12.5, 1.0, -2.001, -2.0001, -1.999], # feature_0 quantized:[3,2,0,0,1] - [2.0, -3.0, 0.5, 0.0, 0.4995], # feature_1 quantized:[2,0,2,1,1] - [3.0, 20.0, 50.0, -100.0, 102.75], # feature_2 quantized:[2,3,3,0,3] - ], - dtype=np.float32) -CLASSIFICATION_LABELS = [[0.], [1.], [1.], [0.], [0.]] -REGRESSION_LABELS = [[1.5], [0.3], [0.2], [2.], [5.]] -FEATURES_DICT = {'f_%d' % i: INPUT_FEATURES[i] for i in range(NUM_FEATURES)} - - -def _make_train_input_fn(is_classification): - """Makes train input_fn for classification/regression.""" - - def _input_fn(): - features_dict = dict(FEATURES_DICT) - labels = CLASSIFICATION_LABELS if is_classification else REGRESSION_LABELS - return features_dict, labels - - return _input_fn - - -def _make_train_input_fn_dataset(is_classification): - """Makes input_fn using Dataset.""" - - def _input_fn(): - features_dict = dict(FEATURES_DICT) - labels = CLASSIFICATION_LABELS if is_classification else REGRESSION_LABELS - ds = dataset_ops.Dataset.zip( - (dataset_ops.Dataset.from_tensors(features_dict), - dataset_ops.Dataset.from_tensors(labels) - )) - return ds - - return _input_fn - - -class BoostedTreesEstimatorTest(test_util.TensorFlowTestCase): - - def setUp(self): - self._head = canned_boosted_trees._create_regression_head(label_dimension=1) - self._feature_columns = { - feature_column.bucketized_column( - feature_column.numeric_column('f_%d' % i, dtype=dtypes.float32), - BUCKET_BOUNDARIES) - for i in range(NUM_FEATURES) - } - - def _assert_checkpoint(self, model_dir, global_step, finalized_trees, - attempted_layers): - reader = checkpoint_utils.load_checkpoint(model_dir) - self.assertEqual(global_step, reader.get_tensor(ops.GraphKeys.GLOBAL_STEP)) - serialized = reader.get_tensor('boosted_trees:0_serialized') - ensemble_proto = boosted_trees_pb2.TreeEnsemble() - ensemble_proto.ParseFromString(serialized) - self.assertEqual( - finalized_trees, - sum([1 for t in ensemble_proto.tree_metadata if t.is_finalized])) - self.assertEqual(attempted_layers, - ensemble_proto.growing_metadata.num_layers_attempted) - - def testTrainAndEvaluateEstimator(self): - input_fn = _make_train_input_fn(is_classification=False) - - est = boosted_trees._BoostedTreesEstimator( - feature_columns=self._feature_columns, - n_batches_per_layer=1, - n_trees=2, - head=self._head, - max_depth=5) - - # It will stop after 10 steps because of the max depth and num trees. - num_steps = 100 - # Train for a few steps, and validate final checkpoint. - est.train(input_fn, steps=num_steps) - self._assert_checkpoint( - est.model_dir, global_step=10, finalized_trees=2, attempted_layers=10) - eval_res = est.evaluate(input_fn=input_fn, steps=1) - self.assertAllClose(eval_res['average_loss'], 1.008551) - - def testTrainAndEvaluateEstimatorWithCenterBias(self): - input_fn = _make_train_input_fn(is_classification=False) - - est = boosted_trees._BoostedTreesEstimator( - feature_columns=self._feature_columns, - n_batches_per_layer=1, - n_trees=2, - head=self._head, - max_depth=5, - center_bias=True) - - # It will stop after 11 steps because of the max depth and num trees. - num_steps = 100 - # Train for a few steps, and validate final checkpoint. - est.train(input_fn, steps=num_steps) - # 10 steps for training and 2 step for bias centering. - self._assert_checkpoint( - est.model_dir, global_step=12, finalized_trees=2, attempted_layers=10) - eval_res = est.evaluate(input_fn=input_fn, steps=1) - self.assertAllClose(eval_res['average_loss'], 0.614642) - - def testTrainAndEvaluateEstimatorWithPrePruning(self): - input_fn = _make_train_input_fn(is_classification=False) - - est = boosted_trees._BoostedTreesEstimator( - feature_columns=self._feature_columns, - n_batches_per_layer=1, - n_trees=2, - head=self._head, - max_depth=5, - tree_complexity=0.001, - pruning_mode='pre') - - num_steps = 100 - # Train for a few steps, and validate final checkpoint. - est.train(input_fn, steps=num_steps) - # We stop actually after 2*depth*n_trees steps (via a hook) because we still - # could not grow 2 trees of depth 5 (due to pre-pruning). - self._assert_checkpoint( - est.model_dir, global_step=21, finalized_trees=0, attempted_layers=21) - eval_res = est.evaluate(input_fn=input_fn, steps=1) - self.assertAllClose(eval_res['average_loss'], 3.83943) - - def testTrainAndEvaluateEstimatorWithPostPruning(self): - input_fn = _make_train_input_fn(is_classification=False) - - est = boosted_trees._BoostedTreesEstimator( - feature_columns=self._feature_columns, - n_batches_per_layer=1, - n_trees=2, - head=self._head, - max_depth=5, - tree_complexity=0.001, - pruning_mode='post') - - # It will stop after 10 steps because of the max depth and num trees. - num_steps = 100 - # Train for a few steps, and validate final checkpoint. - est.train(input_fn, steps=num_steps) - self._assert_checkpoint( - est.model_dir, global_step=10, finalized_trees=2, attempted_layers=10) - eval_res = est.evaluate(input_fn=input_fn, steps=1) - self.assertAllClose(eval_res['average_loss'], 2.37652) - - def testInferEstimator(self): - train_input_fn = _make_train_input_fn(is_classification=False) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees._BoostedTreesEstimator( - feature_columns=self._feature_columns, - n_batches_per_layer=1, - n_trees=1, - max_depth=5, - head=self._head) - - # It will stop after 5 steps because of the max depth and num trees. - num_steps = 100 - # Train for a few steps, and validate final checkpoint. - est.train(train_input_fn, steps=num_steps) - self._assert_checkpoint( - est.model_dir, global_step=5, finalized_trees=1, attempted_layers=5) - # Validate predictions. - predictions = list(est.predict(input_fn=predict_input_fn)) - self.assertAllClose( - [[0.571619], [0.262821], [0.124549], [0.956801], [1.769801]], - [pred['predictions'] for pred in predictions]) - - def testInferEstimatorWithCenterBias(self): - train_input_fn = _make_train_input_fn(is_classification=False) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees._BoostedTreesEstimator( - feature_columns=self._feature_columns, - n_batches_per_layer=1, - n_trees=1, - max_depth=5, - center_bias=True, - head=self._head) - - # It will stop after 6 steps because of the max depth and num trees (5 for - # training and 2 for bias centering). - num_steps = 100 - # Train for a few steps, and validate final checkpoint. - est.train(train_input_fn, steps=num_steps) - self._assert_checkpoint( - est.model_dir, global_step=7, finalized_trees=1, attempted_layers=5) - # Validate predictions. - predictions = list(est.predict(input_fn=predict_input_fn)) - - self.assertAllClose( - [[1.634501], [1.325703], [1.187431], [2.019683], [2.832683]], - [pred['predictions'] for pred in predictions]) - - def testBinaryClassifierTrainInMemoryAndEvalAndInfer(self): - train_input_fn = _make_train_input_fn(is_classification=True) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees.boosted_trees_classifier_train_in_memory( - train_input_fn=train_input_fn, feature_columns=self._feature_columns, - n_trees=1, max_depth=5) - # It will stop after 5 steps because of the max depth and num trees. - self._assert_checkpoint( - est.model_dir, global_step=5, finalized_trees=1, attempted_layers=5) - - # Check evaluate and predict. - eval_res = est.evaluate(input_fn=train_input_fn, steps=1) - self.assertAllClose(eval_res['accuracy'], 1.0) - # Validate predictions. - predictions = list(est.predict(input_fn=predict_input_fn)) - self.assertAllClose([[0], [1], [1], [0], [0]], - [pred['class_ids'] for pred in predictions]) - - def testBinaryClassifierTrainInMemoryAndEvalAndInferWithCenterBias(self): - train_input_fn = _make_train_input_fn(is_classification=True) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees.boosted_trees_classifier_train_in_memory( - train_input_fn=train_input_fn, - feature_columns=self._feature_columns, - n_trees=1, - max_depth=5, - center_bias=True) - # It will stop after 5 steps + 3 for bias, because of the max depth and num - # trees. - self._assert_checkpoint( - est.model_dir, global_step=8, finalized_trees=1, attempted_layers=5) - - # Check evaluate and predict. - eval_res = est.evaluate(input_fn=train_input_fn, steps=1) - self.assertAllClose(eval_res['accuracy'], 1.0) - # Validate predictions. - predictions = list(est.predict(input_fn=predict_input_fn)) - self.assertAllClose([[0], [1], [1], [0], [0]], - [pred['class_ids'] for pred in predictions]) - - def testBinaryClassifierTrainInMemoryAndEvalAndInferWithPrePruning(self): - train_input_fn = _make_train_input_fn(is_classification=True) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees.boosted_trees_classifier_train_in_memory( - train_input_fn=train_input_fn, - feature_columns=self._feature_columns, - n_trees=1, - max_depth=5, - pruning_mode='pre', - tree_complexity=0.01) - # We stop actually after 2*depth*n_trees steps (via a hook) because we still - # could not grow 1 trees of depth 5 (due to pre-pruning). - self._assert_checkpoint( - est.model_dir, global_step=11, finalized_trees=0, attempted_layers=11) - - # Check evaluate and predict. - eval_res = est.evaluate(input_fn=train_input_fn, steps=1) - self.assertAllClose(eval_res['accuracy'], 1.0) - # Validate predictions. - predictions = list(est.predict(input_fn=predict_input_fn)) - self.assertAllClose([[0], [1], [1], [0], [0]], - [pred['class_ids'] for pred in predictions]) - - def testBinaryClassifierTrainInMemoryWithDataset(self): - train_input_fn = _make_train_input_fn_dataset(is_classification=True) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees.boosted_trees_classifier_train_in_memory( - train_input_fn=train_input_fn, - feature_columns=self._feature_columns, - n_trees=1, - max_depth=5) - # It will stop after 5 steps because of the max depth and num trees. - self._assert_checkpoint( - est.model_dir, global_step=5, finalized_trees=1, attempted_layers=5) - - # Check evaluate and predict. - eval_res = est.evaluate(input_fn=train_input_fn, steps=1) - self.assertAllClose(eval_res['accuracy'], 1.0) - predictions = list(est.predict(input_fn=predict_input_fn)) - self.assertAllClose([[0], [1], [1], [0], [0]], - [pred['class_ids'] for pred in predictions]) - - def testRegressorTrainInMemoryAndEvalAndInfer(self): - train_input_fn = _make_train_input_fn(is_classification=False) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees.boosted_trees_regressor_train_in_memory( - train_input_fn=train_input_fn, feature_columns=self._feature_columns, - n_trees=1, max_depth=5) - # It will stop after 5 steps because of the max depth and num trees. - self._assert_checkpoint( - est.model_dir, global_step=5, finalized_trees=1, attempted_layers=5) - - # Check evaluate and predict. - eval_res = est.evaluate(input_fn=train_input_fn, steps=1) - self.assertAllClose(eval_res['average_loss'], 2.478283) - predictions = list(est.predict(input_fn=predict_input_fn)) - self.assertAllClose( - [[0.571619], [0.262821], [0.124549], [0.956801], [1.769801]], - [pred['predictions'] for pred in predictions]) - - def testRegressorTrainInMemoryWithDataset(self): - train_input_fn = _make_train_input_fn_dataset(is_classification=False) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees.boosted_trees_regressor_train_in_memory( - train_input_fn=train_input_fn, feature_columns=self._feature_columns, - n_trees=1, max_depth=5) - # It will stop after 5 steps because of the max depth and num trees. - self._assert_checkpoint( - est.model_dir, global_step=5, finalized_trees=1, attempted_layers=5) - # Check evaluate and predict. - eval_res = est.evaluate(input_fn=train_input_fn, steps=1) - self.assertAllClose(eval_res['average_loss'], 2.478283) - predictions = list(est.predict(input_fn=predict_input_fn)) - self.assertAllClose( - [[0.571619], [0.262821], [0.124549], [0.956801], [1.769801]], - [pred['predictions'] for pred in predictions]) - - -class BoostedTreesDebugOutputTest(test_util.TensorFlowTestCase): - - def setUp(self): - self._head = canned_boosted_trees._create_regression_head(label_dimension=1) - self._feature_columns = { - feature_column.bucketized_column( - feature_column.numeric_column('f_%d' % i, dtype=dtypes.float32), - BUCKET_BOUNDARIES) for i in range(NUM_FEATURES) - } - - def testContribEstimatorThatDFCIsInPredictions(self): - # pylint:disable=protected-access - head = canned_boosted_trees._create_regression_head(label_dimension=1) - train_input_fn = _make_train_input_fn(is_classification=False) - predict_input_fn = numpy_io.numpy_input_fn( - x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False) - - est = boosted_trees._BoostedTreesEstimator( - feature_columns=self._feature_columns, - n_batches_per_layer=1, - head=head, - n_trees=1, - max_depth=5, - center_bias=True) - # pylint:enable=protected-access - - num_steps = 100 - # Train for a few steps. Validate debug outputs in prediction dicts. - est.train(train_input_fn, steps=num_steps) - debug_predictions = est.experimental_predict_with_explanations( - predict_input_fn) - biases, dfcs = zip(*[(pred['bias'], pred['dfc']) - for pred in debug_predictions]) - self.assertAllClose([1.8] * 5, biases) - self.assertAllClose(({ - 0: -0.070499420166015625, - 1: -0.095000028610229492, - 2: 0.0 - }, { - 0: -0.53763031959533691, - 1: 0.063333392143249512, - 2: 0.0 - }, { - 0: -0.51756942272186279, - 1: -0.095000028610229492, - 2: 0.0 - }, { - 0: 0.1563495397567749, - 1: 0.063333392143249512, - 2: 0.0 - }, { - 0: 0.96934974193572998, - 1: 0.063333392143249512, - 2: 0.0 - }), dfcs) - - # Assert sum(dfcs) + bias == predictions. - expected_predictions = [[1.6345005], [1.32570302], [1.1874305], - [2.01968288], [2.83268309]] - predictions = [ - [sum(dfc.values()) + bias] for (dfc, bias) in zip(dfcs, biases) - ] - self.assertAllClose(expected_predictions, predictions) - - # Test when user doesn't include bias or dfc in predict_keys. - debug_predictions = est.experimental_predict_with_explanations( - predict_input_fn, predict_keys=['predictions']) - for prediction_dict in debug_predictions: - self.assertTrue('bias' in prediction_dict) - self.assertTrue('dfc' in prediction_dict) - self.assertTrue('predictions' in prediction_dict) - self.assertEqual(len(prediction_dict), 3) - - -if __name__ == '__main__': - googletest.main() diff --git a/tensorflow/contrib/estimator/python/estimator/dnn.py b/tensorflow/contrib/estimator/python/estimator/dnn.py index 9efa8f474d..10f657df8d 100644 --- a/tensorflow/contrib/estimator/python/estimator/dnn.py +++ b/tensorflow/contrib/estimator/python/estimator/dnn.py @@ -1,4 +1,4 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# 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. @@ -12,153 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Deep Neural Network estimators.""" +"""dnn python module. + +Importing from tensorflow.python.estimator is unsupported +and will soon break! +""" +# pylint: disable=unused-import,g-bad-import-order,g-import-not-at-top,wildcard-import from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.estimator import estimator -from tensorflow.python.estimator.canned import dnn as dnn_lib -from tensorflow.python.ops import nn - - -class DNNEstimator(estimator.Estimator): - """An estimator for TensorFlow DNN models with user-specified head. - - Example: - - ```python - sparse_feature_a = sparse_column_with_hash_bucket(...) - sparse_feature_b = sparse_column_with_hash_bucket(...) - - sparse_feature_a_emb = embedding_column(sparse_id_column=sparse_feature_a, - ...) - sparse_feature_b_emb = embedding_column(sparse_id_column=sparse_feature_b, - ...) - - estimator = DNNEstimator( - head=tf.contrib.estimator.multi_label_head(n_classes=3), - feature_columns=[sparse_feature_a_emb, sparse_feature_b_emb], - hidden_units=[1024, 512, 256]) - - # Or estimator using the ProximalAdagradOptimizer optimizer with - # regularization. - estimator = DNNEstimator( - head=tf.contrib.estimator.multi_label_head(n_classes=3), - feature_columns=[sparse_feature_a_emb, sparse_feature_b_emb], - hidden_units=[1024, 512, 256], - optimizer=tf.train.ProximalAdagradOptimizer( - learning_rate=0.1, - l1_regularization_strength=0.001 - )) - - # Or estimator using an optimizer with a learning rate decay. - estimator = DNNEstimator( - head=tf.contrib.estimator.multi_label_head(n_classes=3), - feature_columns=[sparse_feature_a_emb, sparse_feature_b_emb], - hidden_units=[1024, 512, 256], - optimizer=lambda: tf.AdamOptimizer( - learning_rate=tf.exponential_decay( - learning_rate=0.1, - global_step=tf.get_global_step(), - decay_steps=10000, - decay_rate=0.96)) - - # Or estimator with warm-starting from a previous checkpoint. - estimator = DNNEstimator( - head=tf.contrib.estimator.multi_label_head(n_classes=3), - feature_columns=[sparse_feature_a_emb, sparse_feature_b_emb], - hidden_units=[1024, 512, 256], - warm_start_from="/path/to/checkpoint/dir") - - # Input builders - def input_fn_train: # returns x, y - pass - estimator.train(input_fn=input_fn_train, steps=100) - - def input_fn_eval: # returns x, y - pass - metrics = estimator.evaluate(input_fn=input_fn_eval, steps=10) - def input_fn_predict: # returns x, None - pass - predictions = estimator.predict(input_fn=input_fn_predict) - ``` - - Input of `train` and `evaluate` should have following features, - otherwise there will be a `KeyError`: - - * if `weight_column` is not `None`, a feature with - `key=weight_column` whose value is a `Tensor`. - * for each `column` in `feature_columns`: - - if `column` is a `_CategoricalColumn`, a feature with `key=column.name` - whose `value` is a `SparseTensor`. - - if `column` is a `_WeightedCategoricalColumn`, two features: the first - with `key` the id column name, the second with `key` the weight column - name. Both features' `value` must be a `SparseTensor`. - - if `column` is a `_DenseColumn`, a feature with `key=column.name` - whose `value` is a `Tensor`. - - Loss and predicted output are determined by the specified head. - """ +from tensorflow_estimator.contrib.estimator.python.estimator import dnn - def __init__(self, - head, - hidden_units, - feature_columns, - model_dir=None, - optimizer='Adagrad', - activation_fn=nn.relu, - dropout=None, - input_layer_partitioner=None, - config=None, - warm_start_from=None, - batch_norm=False): - """Initializes a `DNNEstimator` instance. +# Include attrs that start with single underscore. +_HAS_DYNAMIC_ATTRIBUTES = True +dnn.__all__ = [s for s in dir(dnn) if not s.startswith('__')] - Args: - head: A `_Head` instance constructed with a method such as - `tf.contrib.estimator.multi_label_head`. - hidden_units: Iterable of number hidden units per layer. All layers are - fully connected. Ex. `[64, 32]` means first layer has 64 nodes and - second one has 32. - feature_columns: An iterable containing all the feature columns used by - the model. All items in the set should be instances of classes derived - from `_FeatureColumn`. - model_dir: Directory to save model parameters, graph and etc. This can - also be used to load checkpoints from the directory into a estimator to - continue training a previously saved model. - optimizer: An instance of `tf.Optimizer` used to train the model. Can also - be a string (one of 'Adagrad', 'Adam', 'Ftrl', 'RMSProp', 'SGD'), or - callable. Defaults to Adagrad optimizer. - activation_fn: Activation function applied to each layer. If `None`, will - use `tf.nn.relu`. - dropout: When not `None`, the probability we will drop out a given - coordinate. - input_layer_partitioner: Optional. Partitioner for input layer. Defaults - to `min_max_variable_partitioner` with `min_slice_size` 64 << 20. - config: `RunConfig` object to configure the runtime settings. - warm_start_from: A string filepath to a checkpoint to warm-start from, or - a `WarmStartSettings` object to fully configure warm-starting. If the - string filepath is provided instead of a `WarmStartSettings`, then all - weights are warm-started, and it is assumed that vocabularies and Tensor - names are unchanged. - batch_norm: Whether to use batch normalization after each hidden layer. - """ - def _model_fn(features, labels, mode, config): - return dnn_lib._dnn_model_fn( # pylint: disable=protected-access - features=features, - labels=labels, - mode=mode, - head=head, - hidden_units=hidden_units, - feature_columns=tuple(feature_columns or []), - optimizer=optimizer, - activation_fn=activation_fn, - dropout=dropout, - input_layer_partitioner=input_layer_partitioner, - config=config, - batch_norm=batch_norm) - super(DNNEstimator, self).__init__( - model_fn=_model_fn, model_dir=model_dir, config=config, - warm_start_from=warm_start_from) +from tensorflow_estimator.contrib.estimator.python.estimator.dnn import * diff --git a/tensorflow/contrib/estimator/python/estimator/dnn_linear_combined.py b/tensorflow/contrib/estimator/python/estimator/dnn_linear_combined.py index 4e7965ef26..7894418c4a 100644 --- a/tensorflow/contrib/estimator/python/estimator/dnn_linear_combined.py +++ b/tensorflow/contrib/estimator/python/estimator/dnn_linear_combined.py @@ -1,4 +1,4 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# 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. @@ -12,171 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""TensorFlow estimator for Linear and DNN joined training models.""" +"""dnn_linear_combined python module. + +Importing from tensorflow.python.estimator is unsupported +and will soon break! +""" +# pylint: disable=unused-import,g-bad-import-order,g-import-not-at-top,wildcard-import from __future__ import absolute_import from __future__ import division from __future__ import print_function -from tensorflow.python.estimator import estimator -from tensorflow.python.estimator.canned import dnn_linear_combined as dnn_linear_combined_lib -from tensorflow.python.ops import nn - - -class DNNLinearCombinedEstimator(estimator.Estimator): - """An estimator for TensorFlow Linear and DNN joined models with custom head. - - Note: This estimator is also known as wide-n-deep. - - Example: - - ```python - numeric_feature = numeric_column(...) - categorical_column_a = categorical_column_with_hash_bucket(...) - categorical_column_b = categorical_column_with_hash_bucket(...) - - categorical_feature_a_x_categorical_feature_b = crossed_column(...) - categorical_feature_a_emb = embedding_column( - categorical_column=categorical_feature_a, ...) - categorical_feature_b_emb = embedding_column( - categorical_column=categorical_feature_b, ...) - - estimator = DNNLinearCombinedEstimator( - head=tf.contrib.estimator.multi_label_head(n_classes=3), - # wide settings - linear_feature_columns=[categorical_feature_a_x_categorical_feature_b], - linear_optimizer=tf.train.FtrlOptimizer(...), - # deep settings - dnn_feature_columns=[ - categorical_feature_a_emb, categorical_feature_b_emb, - numeric_feature], - dnn_hidden_units=[1000, 500, 100], - dnn_optimizer=tf.train.ProximalAdagradOptimizer(...)) - - # To apply L1 and L2 regularization, you can set dnn_optimizer to: - tf.train.ProximalAdagradOptimizer( - learning_rate=0.1, - l1_regularization_strength=0.001, - l2_regularization_strength=0.001) - # To apply learning rate decay, you can set dnn_optimizer to a callable: - lambda: tf.AdamOptimizer( - learning_rate=tf.exponential_decay( - learning_rate=0.1, - global_step=tf.get_global_step(), - decay_steps=10000, - decay_rate=0.96) - # It is the same for linear_optimizer. - - # Input builders - def input_fn_train: # returns x, y - pass - estimator.train(input_fn=input_fn_train, steps=100) - - def input_fn_eval: # returns x, y - pass - metrics = estimator.evaluate(input_fn=input_fn_eval, steps=10) - def input_fn_predict: # returns x, None - pass - predictions = estimator.predict(input_fn=input_fn_predict) - ``` - - Input of `train` and `evaluate` should have following features, - otherwise there will be a `KeyError`: - - * for each `column` in `dnn_feature_columns` + `linear_feature_columns`: - - if `column` is a `_CategoricalColumn`, a feature with `key=column.name` - whose `value` is a `SparseTensor`. - - if `column` is a `_WeightedCategoricalColumn`, two features: the first - with `key` the id column name, the second with `key` the weight column - name. Both features' `value` must be a `SparseTensor`. - - if `column` is a `_DenseColumn`, a feature with `key=column.name` - whose `value` is a `Tensor`. - - Loss is calculated by using mean squared error. - - @compatibility(eager) - Estimators are not compatible with eager execution. - @end_compatibility - """ - - def __init__(self, - head, - model_dir=None, - linear_feature_columns=None, - linear_optimizer='Ftrl', - dnn_feature_columns=None, - dnn_optimizer='Adagrad', - dnn_hidden_units=None, - dnn_activation_fn=nn.relu, - dnn_dropout=None, - input_layer_partitioner=None, - config=None, - linear_sparse_combiner='sum'): - """Initializes a DNNLinearCombinedEstimator instance. - - Args: - head: A `_Head` instance constructed with a method such as - `tf.contrib.estimator.multi_label_head`. - model_dir: Directory to save model parameters, graph and etc. This can - also be used to load checkpoints from the directory into an estimator - to continue training a previously saved model. - linear_feature_columns: An iterable containing all the feature columns - used by linear part of the model. All items in the set must be - instances of classes derived from `FeatureColumn`. - linear_optimizer: An instance of `tf.Optimizer` used to apply gradients to - the linear part of the model. Can also be a string (one of 'Adagrad', - 'Adam', 'Ftrl', 'RMSProp', 'SGD'), or callable. Defaults to FTRL - optimizer. - dnn_feature_columns: An iterable containing all the feature columns used - by deep part of the model. All items in the set must be instances of - classes derived from `FeatureColumn`. - dnn_optimizer: An instance of `tf.Optimizer` used to apply gradients to - the deep part of the model. Can also be a string (one of 'Adagrad', - 'Adam', 'Ftrl', 'RMSProp', 'SGD'), or callable. Defaults to Adagrad - optimizer. - dnn_hidden_units: List of hidden units per layer. All layers are fully - connected. - dnn_activation_fn: Activation function applied to each layer. If None, - will use `tf.nn.relu`. - dnn_dropout: When not None, the probability we will drop out - a given coordinate. - input_layer_partitioner: Partitioner for input layer. Defaults to - `min_max_variable_partitioner` with `min_slice_size` 64 << 20. - config: RunConfig object to configure the runtime settings. - linear_sparse_combiner: A string specifying how to reduce the linear model - if a categorical column is multivalent. One of "mean", "sqrtn", and - "sum" -- these are effectively different ways to do example-level - normalization, which can be useful for bag-of-words features. For more - details, see `tf.feature_column.linear_model`. - - Raises: - ValueError: If both linear_feature_columns and dnn_features_columns are - empty at the same time. - """ - linear_feature_columns = linear_feature_columns or [] - dnn_feature_columns = dnn_feature_columns or [] - self._feature_columns = ( - list(linear_feature_columns) + list(dnn_feature_columns)) - if not self._feature_columns: - raise ValueError('Either linear_feature_columns or dnn_feature_columns ' - 'must be defined.') +from tensorflow_estimator.contrib.estimator.python.estimator import dnn_linear_combined - def _model_fn(features, labels, mode, config): - return dnn_linear_combined_lib._dnn_linear_combined_model_fn( # pylint: disable=protected-access - features=features, - labels=labels, - mode=mode, - head=head, - linear_feature_columns=linear_feature_columns, - linear_optimizer=linear_optimizer, - dnn_feature_columns=dnn_feature_columns, - dnn_optimizer=dnn_optimizer, - dnn_hidden_units=dnn_hidden_units, - dnn_activation_fn=dnn_activation_fn, - dnn_dropout=dnn_dropout, - input_layer_partitioner=input_layer_partitioner, - config=config, - linear_sparse_combiner=linear_sparse_combiner) +# Include attrs that start with single underscore. +_HAS_DYNAMIC_ATTRIBUTES = True +dnn_linear_combined.__all__ = [ + s for s in dir(dnn_linear_combined) if not s.startswith('__') +] - super(DNNLinearCombinedEstimator, self).__init__( - model_fn=_model_fn, model_dir=model_dir, config=config) +from tensorflow_estimator.contrib.estimator.python.estimator.dnn_linear_combined import * diff --git a/tensorflow/contrib/estimator/python/estimator/dnn_linear_combined_test.py b/tensorflow/contrib/estimator/python/estimator/dnn_linear_combined_test.py deleted file mode 100644 index 51b9ce7005..0000000000 --- a/tensorflow/contrib/estimator/python/estimator/dnn_linear_combined_test.py +++ /dev/null @@ -1,227 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for dnn_linear_combined.py.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import shutil -import tempfile - -import numpy as np -import six - -from tensorflow.contrib.estimator.python.estimator import dnn_linear_combined -from tensorflow.contrib.estimator.python.estimator import head as head_lib -from tensorflow.python.estimator.canned import dnn_testing_utils -from tensorflow.python.estimator.canned import linear_testing_utils -from tensorflow.python.estimator.canned import prediction_keys -from tensorflow.python.estimator.export import export -from tensorflow.python.estimator.inputs import numpy_io -from tensorflow.python.feature_column import feature_column -from tensorflow.python.framework import ops -from tensorflow.python.ops import nn -from tensorflow.python.ops.losses import losses -from tensorflow.python.platform import gfile -from tensorflow.python.platform import test -from tensorflow.python.summary.writer import writer_cache - - -def _dnn_only_estimator_fn( - hidden_units, - feature_columns, - model_dir=None, - label_dimension=1, - weight_column=None, - optimizer='Adagrad', - activation_fn=nn.relu, - dropout=None, - input_layer_partitioner=None, - config=None): - return dnn_linear_combined.DNNLinearCombinedEstimator( - head=head_lib.regression_head( - weight_column=weight_column, label_dimension=label_dimension, - # Tests in core (from which this test inherits) test the sum loss. - loss_reduction=losses.Reduction.SUM), - model_dir=model_dir, - dnn_feature_columns=feature_columns, - dnn_optimizer=optimizer, - dnn_hidden_units=hidden_units, - dnn_activation_fn=activation_fn, - dnn_dropout=dropout, - input_layer_partitioner=input_layer_partitioner, - config=config) - - -class DNNOnlyEstimatorEvaluateTest( - dnn_testing_utils.BaseDNNRegressorEvaluateTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - dnn_testing_utils.BaseDNNRegressorEvaluateTest.__init__( - self, _dnn_only_estimator_fn) - - -class DNNOnlyEstimatorPredictTest( - dnn_testing_utils.BaseDNNRegressorPredictTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - dnn_testing_utils.BaseDNNRegressorPredictTest.__init__( - self, _dnn_only_estimator_fn) - - -class DNNOnlyEstimatorTrainTest( - dnn_testing_utils.BaseDNNRegressorTrainTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - dnn_testing_utils.BaseDNNRegressorTrainTest.__init__( - self, _dnn_only_estimator_fn) - - -def _linear_only_estimator_fn( - feature_columns, - model_dir=None, - label_dimension=1, - weight_column=None, - optimizer='Ftrl', - config=None, - partitioner=None, - sparse_combiner='sum'): - return dnn_linear_combined.DNNLinearCombinedEstimator( - head=head_lib.regression_head( - weight_column=weight_column, label_dimension=label_dimension, - # Tests in core (from which this test inherits) test the sum loss. - loss_reduction=losses.Reduction.SUM), - model_dir=model_dir, - linear_feature_columns=feature_columns, - linear_optimizer=optimizer, - input_layer_partitioner=partitioner, - config=config, - linear_sparse_combiner=sparse_combiner) - - -class LinearOnlyEstimatorEvaluateTest( - linear_testing_utils.BaseLinearRegressorEvaluationTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - linear_testing_utils.BaseLinearRegressorEvaluationTest.__init__( - self, _linear_only_estimator_fn) - - -class LinearOnlyEstimatorPredictTest( - linear_testing_utils.BaseLinearRegressorPredictTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - linear_testing_utils.BaseLinearRegressorPredictTest.__init__( - self, _linear_only_estimator_fn) - - -class LinearOnlyEstimatorTrainTest( - linear_testing_utils.BaseLinearRegressorTrainingTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - linear_testing_utils.BaseLinearRegressorTrainingTest.__init__( - self, _linear_only_estimator_fn) - - -class DNNLinearCombinedEstimatorIntegrationTest(test.TestCase): - - def setUp(self): - self._model_dir = tempfile.mkdtemp() - - def tearDown(self): - if self._model_dir: - writer_cache.FileWriterCache.clear() - shutil.rmtree(self._model_dir) - - def _test_complete_flow( - self, train_input_fn, eval_input_fn, predict_input_fn, input_dimension, - label_dimension, batch_size): - linear_feature_columns = [ - feature_column.numeric_column('x', shape=(input_dimension,))] - dnn_feature_columns = [ - feature_column.numeric_column('x', shape=(input_dimension,))] - feature_columns = linear_feature_columns + dnn_feature_columns - est = dnn_linear_combined.DNNLinearCombinedEstimator( - head=head_lib.regression_head(label_dimension=label_dimension), - linear_feature_columns=linear_feature_columns, - dnn_feature_columns=dnn_feature_columns, - dnn_hidden_units=(2, 2), - model_dir=self._model_dir) - - # TRAIN - num_steps = 10 - est.train(train_input_fn, steps=num_steps) - - # EVALUTE - scores = est.evaluate(eval_input_fn) - self.assertEqual(num_steps, scores[ops.GraphKeys.GLOBAL_STEP]) - self.assertIn('loss', six.iterkeys(scores)) - - # PREDICT - predictions = np.array([ - x[prediction_keys.PredictionKeys.PREDICTIONS] - for x in est.predict(predict_input_fn) - ]) - self.assertAllEqual((batch_size, label_dimension), predictions.shape) - - # EXPORT - feature_spec = feature_column.make_parse_example_spec(feature_columns) - serving_input_receiver_fn = export.build_parsing_serving_input_receiver_fn( - feature_spec) - export_dir = est.export_savedmodel(tempfile.mkdtemp(), - serving_input_receiver_fn) - self.assertTrue(gfile.Exists(export_dir)) - - def test_numpy_input_fn(self): - """Tests complete flow with numpy_input_fn.""" - label_dimension = 2 - batch_size = 10 - data = np.linspace(0., 2., batch_size * label_dimension, dtype=np.float32) - data = data.reshape(batch_size, label_dimension) - # learn y = x - train_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - y=data, - batch_size=batch_size, - num_epochs=None, - shuffle=True) - eval_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - y=data, - batch_size=batch_size, - shuffle=False) - predict_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - batch_size=batch_size, - shuffle=False) - - self._test_complete_flow( - train_input_fn=train_input_fn, - eval_input_fn=eval_input_fn, - predict_input_fn=predict_input_fn, - input_dimension=label_dimension, - label_dimension=label_dimension, - batch_size=batch_size) - - -if __name__ == '__main__': - test.main() diff --git a/tensorflow/contrib/estimator/python/estimator/dnn_test.py b/tensorflow/contrib/estimator/python/estimator/dnn_test.py deleted file mode 100644 index 050b0428bf..0000000000 --- a/tensorflow/contrib/estimator/python/estimator/dnn_test.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Tests for dnn.py.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import shutil -import tempfile - -import numpy as np -import six - -from tensorflow.contrib.estimator.python.estimator import dnn -from tensorflow.contrib.estimator.python.estimator import head as head_lib -from tensorflow.python.estimator.canned import dnn_testing_utils -from tensorflow.python.estimator.canned import prediction_keys -from tensorflow.python.estimator.export import export -from tensorflow.python.estimator.inputs import numpy_io -from tensorflow.python.feature_column import feature_column -from tensorflow.python.framework import ops -from tensorflow.python.ops.losses import losses -from tensorflow.python.platform import gfile -from tensorflow.python.platform import test -from tensorflow.python.summary.writer import writer_cache - - -def _dnn_estimator_fn(weight_column=None, label_dimension=1, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg - """Returns a DNNEstimator that uses regression_head.""" - return dnn.DNNEstimator( - head=head_lib.regression_head( - weight_column=weight_column, label_dimension=label_dimension, - # Tests in core (from which this test inherits) test the sum loss. - loss_reduction=losses.Reduction.SUM), - *args, **kwargs) - - -def _dnn_estimator_classifier_fn(n_classes=3, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg - """Returns a DNNEstimator that uses multi_class_head.""" - return dnn.DNNEstimator(head=head_lib.multi_class_head(n_classes=n_classes), - *args, **kwargs) - - -class DNNEstimatorEvaluateTest( - dnn_testing_utils.BaseDNNRegressorEvaluateTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - dnn_testing_utils.BaseDNNRegressorEvaluateTest.__init__( - self, _dnn_estimator_fn) - - -class DNNEstimatorPredictTest( - dnn_testing_utils.BaseDNNRegressorPredictTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - dnn_testing_utils.BaseDNNRegressorPredictTest.__init__( - self, _dnn_estimator_fn) - - -class DNNEstimatorTrainTest( - dnn_testing_utils.BaseDNNRegressorTrainTest, test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - dnn_testing_utils.BaseDNNRegressorTrainTest.__init__( - self, _dnn_estimator_fn) - - -class DNNEstimatorWarmStartingTest(dnn_testing_utils.BaseDNNWarmStartingTest, - test.TestCase): - - def __init__(self, methodName='runTest'): # pylint: disable=invalid-name - test.TestCase.__init__(self, methodName) - dnn_testing_utils.BaseDNNWarmStartingTest.__init__( - self, _dnn_estimator_classifier_fn, _dnn_estimator_fn) - - -class DNNEstimatorIntegrationTest(test.TestCase): - - def setUp(self): - self._model_dir = tempfile.mkdtemp() - - def tearDown(self): - if self._model_dir: - writer_cache.FileWriterCache.clear() - shutil.rmtree(self._model_dir) - - def _test_complete_flow( - self, train_input_fn, eval_input_fn, predict_input_fn, input_dimension, - label_dimension, batch_size): - feature_columns = [ - feature_column.numeric_column('x', shape=(input_dimension,))] - est = dnn.DNNEstimator( - head=head_lib.regression_head(label_dimension=label_dimension), - hidden_units=(2, 2), - feature_columns=feature_columns, - model_dir=self._model_dir) - - # TRAIN - num_steps = 10 - est.train(train_input_fn, steps=num_steps) - - # EVALUTE - scores = est.evaluate(eval_input_fn) - self.assertEqual(num_steps, scores[ops.GraphKeys.GLOBAL_STEP]) - self.assertIn('loss', six.iterkeys(scores)) - - # PREDICT - predictions = np.array([ - x[prediction_keys.PredictionKeys.PREDICTIONS] - for x in est.predict(predict_input_fn) - ]) - self.assertAllEqual((batch_size, label_dimension), predictions.shape) - - # EXPORT - feature_spec = feature_column.make_parse_example_spec(feature_columns) - serving_input_receiver_fn = export.build_parsing_serving_input_receiver_fn( - feature_spec) - export_dir = est.export_savedmodel(tempfile.mkdtemp(), - serving_input_receiver_fn) - self.assertTrue(gfile.Exists(export_dir)) - - def test_numpy_input_fn(self): - """Tests complete flow with numpy_input_fn.""" - label_dimension = 2 - batch_size = 10 - data = np.linspace(0., 2., batch_size * label_dimension, dtype=np.float32) - data = data.reshape(batch_size, label_dimension) - # learn y = x - train_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - y=data, - batch_size=batch_size, - num_epochs=None, - shuffle=True) - eval_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - y=data, - batch_size=batch_size, - shuffle=False) - predict_input_fn = numpy_io.numpy_input_fn( - x={'x': data}, - batch_size=batch_size, - shuffle=False) - - self._test_complete_flow( - train_input_fn=train_input_fn, - eval_input_fn=eval_input_fn, - predict_input_fn=predict_input_fn, - input_dimension=label_dimension, - label_dimension=label_dimension, - batch_size=batch_size) - - -if __name__ == '__main__': - test.main() diff --git a/tensorflow/contrib/estimator/python/estimator/dnn_with_layer_annotations.py b/tensorflow/contrib/estimator/python/estimator/dnn_with_layer_annotations.py index 40a91175b7..854d2e4011 100644 --- a/tensorflow/contrib/estimator/python/estimator/dnn_with_layer_annotations.py +++ b/tensorflow/contrib/estimator/python/estimator/dnn_with_layer_annotations.py @@ -1,4 +1,4 @@ -# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# 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. @@ -12,425 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Deep Neural Network estimators with layer annotations.""" +"""dnn_with_layer_annotations python module. + +Importing from tensorflow.python.estimator is unsupported +and will soon break! +""" +# pylint: disable=unused-import,g-bad-import-order,g-import-not-at-top,wildcard-import from __future__ import absolute_import from __future__ import division from __future__ import print_function -import contextlib -import pickle - -from google.protobuf.any_pb2 import Any - -from tensorflow.python.estimator import estimator -from tensorflow.python.estimator.canned import dnn -from tensorflow.python.feature_column import feature_column as feature_column_lib -from tensorflow.python.framework import ops -from tensorflow.python.ops import nn -from tensorflow.python.ops.losses import losses -from tensorflow.python.saved_model import utils as saved_model_utils - - -class LayerAnnotationsCollectionNames(object): - """Names for the collections containing the annotations.""" - - UNPROCESSED_FEATURES = 'layer_annotations/unprocessed_features' - PROCESSED_FEATURES = 'layer_annotatons/processed_features' - FEATURE_COLUMNS = 'layer_annotations/feature_columns' - - @classmethod - def keys(cls, collection_name): - return '%s/keys' % collection_name - - @classmethod - def values(cls, collection_name): - return '%s/values' % collection_name - - -def serialize_feature_column(feature_column): - if isinstance(feature_column, feature_column_lib._EmbeddingColumn): # pylint: disable=protected-access - # We can't pickle nested functions, and we don't need the value of - # layer_creator in most cases anyway, so just discard its value. - args = feature_column._asdict() - args['layer_creator'] = None - temp = type(feature_column)(**args) - return pickle.dumps(temp) - return pickle.dumps(feature_column) - - -def _to_any_wrapped_tensor_info(tensor): - """Converts a `Tensor` to a `TensorInfo` wrapped in a proto `Any`.""" - any_buf = Any() - tensor_info = saved_model_utils.build_tensor_info(tensor) - any_buf.Pack(tensor_info) - return any_buf - - -def make_input_layer_with_layer_annotations(original_input_layer): - """Make an input_layer replacement function that adds layer annotations.""" - - def input_layer_with_layer_annotations(features, - feature_columns, - weight_collections=None, - trainable=True, - cols_to_vars=None, - scope=None, - cols_to_output_tensors=None, - from_template=False): - """Returns a dense `Tensor` as input layer based on given `feature_columns`. - - Generally a single example in training data is described with - FeatureColumns. - At the first layer of the model, this column oriented data should be - converted - to a single `Tensor`. - - This is like tf.feature_column.input_layer, except with added - Integrated-Gradient annotations. - - Args: - features: A mapping from key to tensors. `_FeatureColumn`s look up via - these keys. For example `numeric_column('price')` will look at 'price' - key in this dict. Values can be a `SparseTensor` or a `Tensor` depends - on corresponding `_FeatureColumn`. - feature_columns: An iterable containing the FeatureColumns to use as - inputs to your model. All items should be instances of classes derived - from `_DenseColumn` such as `numeric_column`, `embedding_column`, - `bucketized_column`, `indicator_column`. If you have categorical - features, you can wrap them with an `embedding_column` or - `indicator_column`. - weight_collections: A list of collection names to which the Variable will - be added. Note that variables will also be added to collections - `tf.GraphKeys.GLOBAL_VARIABLES` and `ops.GraphKeys.MODEL_VARIABLES`. - trainable: If `True` also add the variable to the graph collection - `GraphKeys.TRAINABLE_VARIABLES` (see `tf.Variable`). - cols_to_vars: If not `None`, must be a dictionary that will be filled with - a mapping from `_FeatureColumn` to list of `Variable`s. For example, - after the call, we might have cols_to_vars = {_EmbeddingColumn( - categorical_column=_HashedCategoricalColumn( key='sparse_feature', - hash_bucket_size=5, dtype=tf.string), dimension=10): [