aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/compiler/tests/randomized_tests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tensorflow/compiler/tests/randomized_tests.cc')
-rw-r--r--tensorflow/compiler/tests/randomized_tests.cc2097
1 files changed, 2097 insertions, 0 deletions
diff --git a/tensorflow/compiler/tests/randomized_tests.cc b/tensorflow/compiler/tests/randomized_tests.cc
new file mode 100644
index 0000000000..41403858a6
--- /dev/null
+++ b/tensorflow/compiler/tests/randomized_tests.cc
@@ -0,0 +1,2097 @@
+/* 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.
+==============================================================================*/
+
+// Randomized tests for XLA implementations of Tensorflow operations.
+//
+// For each operator, the tests in this file choose a set of random inputs and
+// attributes. The test then compares the outputs of the operator when executed
+// via Tensorflow using the CPU device and when executed via XLA.
+//
+// By default, each test chooses a random seed nondeterministically (using
+// std::random_device). However, a particular choice of random seed can be
+// forced using the flag --tf_xla_random_seed; each test logs the
+// flag value necessary to reproduce its outputs.
+//
+// Example usage:
+// Run tests, comparing the Tensorflow CPU operators with their XLA-compiled
+// counterparts:
+// randomized_tests \
+// --tf_xla_test_use_jit=true --tf_xla_test_device=CPU \
+// --tf_xla_test_repetitions=20
+
+// TODO(phawkins): add tests for:
+// * ArgMax
+// * DepthwiseConv2DNative
+// * Gather
+// * InvertPermutation
+// * MaxPoolGrad (requires implementation of forward operator)
+// * Select
+// * Unpack
+//
+// TODO(phawkins): improve tests for:
+// * StridedSliceGrad (need to use shape function to compute sensible inputs)
+
+#include <random>
+#include <unordered_map>
+
+#include "tensorflow/compiler/jit/defs.h"
+#include "tensorflow/compiler/tf2xla/type_util.h"
+#include "tensorflow/core/common_runtime/device.h"
+#include "tensorflow/core/common_runtime/device_factory.h"
+#include "tensorflow/core/framework/node_def_builder.h"
+#include "tensorflow/core/framework/node_def_util.h"
+#include "tensorflow/core/framework/op_kernel.h"
+#include "tensorflow/core/framework/tensor.h"
+#include "tensorflow/core/framework/tensor_testutil.h"
+#include "tensorflow/core/framework/types.pb.h"
+#include "tensorflow/core/graph/graph.h"
+#include "tensorflow/core/graph/graph_constructor.h"
+#include "tensorflow/core/kernels/ops_util.h"
+#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/lib/core/stringpiece.h"
+#include "tensorflow/core/platform/test.h"
+#include "tensorflow/core/public/session.h"
+#include "tensorflow/core/public/session_options.h"
+#include "tensorflow/core/util/command_line_flags.h"
+
+namespace tensorflow {
+namespace {
+
+// Command line flags: see main() below.
+int64 tf_xla_random_seed = 0;
+int32 tf_xla_test_repetitions = 20;
+string* tf_xla_test_device_ptr; // initial value set in main()
+bool tf_xla_test_use_jit = true;
+
+string DeviceTypeToDeviceName(DeviceType type) {
+ return strings::StrCat("/job:localhost/replica:0/task:0/device:", type.type(),
+ ":0");
+}
+
+constexpr std::array<DataType, 3> kAllXlaTypes = {
+ {DT_INT32, DT_FLOAT, DT_BOOL}};
+
+// An OpTestBuilder is a graph builder class that takes as input an operator to
+// test, its inputs and attributes, and builds a graph that executes the
+// operator.
+class OpTestBuilder {
+ public:
+ explicit OpTestBuilder(const string& op_name);
+
+ // Adds an input 'tensor'.
+ OpTestBuilder& Input(Tensor tensor);
+
+ // Sets an attribute.
+ template <class T>
+ OpTestBuilder& Attr(StringPiece attr_name, T&& value);
+
+ // Overload needed to allow {...} expressions for value.
+ template <class T>
+ OpTestBuilder& Attr(StringPiece attr_name, std::initializer_list<T> value);
+
+ // Adds nodes that executes the operator under test on 'device' to 'graphdef'.
+ // If 'use_jit' is true, marks the operator under test to be compiled by XLA.
+ // The graph will consist of one Placeholder node per input, the operator
+ // itself, and one Identity node per output. If 'test_node_def' is not null,
+ // sets it to the NodeDef of the operator under test. Fills 'inputs' and
+ // 'outputs' with the names of the input placeholder nodes and the output
+ // identity nodes, respectively.
+ Status BuildGraph(string name_prefix, string device, bool use_jit,
+ GraphDef* graphdef, NodeDef** test_node_def,
+ std::vector<string>* inputs,
+ std::vector<string>* outputs) const;
+
+ const std::vector<Tensor>& inputs() const { return inputs_; }
+
+ private:
+ NodeDef node_def_;
+ std::vector<Tensor> inputs_;
+};
+
+OpTestBuilder::OpTestBuilder(const string& op_name) {
+ node_def_.set_op(op_name);
+}
+
+OpTestBuilder& OpTestBuilder::Input(Tensor tensor) {
+ VLOG(1) << "Adding input: " << tensor.DebugString();
+ inputs_.push_back(tensor);
+ return *this;
+}
+
+template <class T>
+OpTestBuilder& OpTestBuilder::Attr(StringPiece attr_name, T&& value) {
+ AddNodeAttr(attr_name, std::forward<T>(value), &node_def_);
+ return *this;
+}
+
+template <class T>
+OpTestBuilder& OpTestBuilder::Attr(StringPiece attr_name,
+ std::initializer_list<T> value) {
+ Attr<std::initializer_list<T>>(attr_name, std::move(value));
+ return *this;
+}
+
+Status OpTestBuilder::BuildGraph(string name_prefix, string device,
+ bool use_jit, GraphDef* graphdef,
+ NodeDef** test_node_def,
+ std::vector<string>* inputs,
+ std::vector<string>* outputs) const {
+ OpRegistryInterface* op_registry = OpRegistry::Global();
+
+ const OpDef* op_def;
+ TF_RETURN_IF_ERROR(op_registry->LookUpOpDef(node_def_.op(), &op_def));
+
+ NodeDef* test_def = graphdef->add_node();
+ *test_def = node_def_;
+ test_def->set_name(strings::StrCat(name_prefix, "_op_under_test"));
+ test_def->set_device(device);
+ AddDefaultsToNodeDef(*op_def, test_def);
+ if (use_jit) {
+ AddNodeAttr(kXlaCompileAttr, true, test_def);
+ }
+ VLOG(1) << "Op under test: " << test_def->DebugString();
+
+ DataTypeVector input_types, output_types;
+ TF_RETURN_IF_ERROR(
+ InOutTypesForNode(*test_def, *op_def, &input_types, &output_types));
+
+ // Build feed and fetch nodes.
+ for (int i = 0; i < input_types.size(); ++i) {
+ NodeDef* def = graphdef->add_node();
+ string name = strings::StrCat(name_prefix, "_input_", i);
+ TF_RETURN_IF_ERROR(NodeDefBuilder(name, "Placeholder")
+ .Device(device)
+ .Attr("dtype", input_types[i])
+ .Finalize(def));
+ inputs->push_back(name);
+ test_def->add_input(name);
+ }
+
+ for (int i = 0; i < output_types.size(); ++i) {
+ NodeDef* def = graphdef->add_node();
+ string name = strings::StrCat(name_prefix, "_output_", i);
+ TF_RETURN_IF_ERROR(NodeDefBuilder(name, "Identity")
+ .Device(device)
+ .Attr("T", output_types[i])
+ .Input(test_def->name(), i, output_types[i])
+ .Finalize(def));
+ outputs->push_back(name);
+ }
+
+ if (test_node_def) {
+ *test_node_def = test_def;
+ }
+
+ return Status::OK();
+}
+
+// Test fixture. The fixture manages the random number generator and its seed,
+// and has a number of convenience methods for building random Tensors, shapes,
+// etc.
+class OpTest : public ::testing::Test {
+ public:
+ OpTest();
+
+ // Runs 'fn' up to --tf_xla_test_repetitions times, or until a failure occurs;
+ // whichever happens first.
+ void Repeatedly(std::function<void(void)> fn);
+
+ // Select a random element from 'candidates'.
+ template <typename T>
+ T Choose(gtl::ArraySlice<T> candidates);
+
+ static constexpr int kDefaultMaxRank = 5;
+ static constexpr int64 kDefaultMaxDimensionSize = 20LL;
+
+ // Returns a random dimension size.
+ int64 RandomDim(int64 min = 0, int64 max = kDefaultMaxDimensionSize);
+
+ // Returns a random shape. The tensor has rank in the range [min_rank,
+ // max_rank).
+ // Each dimension has size [0, kDefaultMaxDimensionSize].
+ std::vector<int64> RandomDims(int min_rank = 0,
+ int max_rank = kDefaultMaxRank,
+ int64 min_size = 0,
+ int64 max_size = kDefaultMaxDimensionSize);
+
+ // Given a shape 'dims', build a pair of dimensions such that one broadcasts
+ // to the other.
+ std::pair<std::vector<int64>, std::vector<int64>> BroadcastableDims(
+ std::vector<int64> dims);
+
+ // Builds a random pair of broadcastable dims.
+ // TODO(phawkins): currently the maximum rank is 3, because broadcasting > 3
+ // dimensions is unimplemented by the Tensorflow Eigen code (b/29268487)
+ std::pair<std::vector<int64>, std::vector<int64>> BroadcastableDims();
+
+ // Returns a tensor filled with random but "reasonable" values from the middle
+ // of the type's range. If the shape is omitted, a random shape is used.
+ // TODO(phawkins): generalize this code to a caller-supplied distribution.
+ Tensor RandomTensor(DataType dtype, gtl::ArraySlice<int64> shape);
+ Tensor RandomTensor(DataType dtype);
+
+ // Like RandomTensor, but uses values >= 0.
+ Tensor RandomNonNegativeTensor(DataType dtype, gtl::ArraySlice<int64> shape);
+ Tensor RandomNonNegativeTensor(DataType dtype);
+
+ // Returns a random subset of the integers in the range [0, rank), suitable
+ // for use as reduction indices.
+ Tensor RandomReductionIndices(int rank);
+
+ struct WindowedDims {
+ Padding padding;
+ int kernel_rows, kernel_cols;
+ int stride_rows, stride_cols;
+ int input_rows, input_cols;
+ int64 output_rows, output_cols;
+ };
+ // Choose dimensions for a 2D windowed op such as pooling or convolution.
+ // TODO(phawkins): currently this only produces spatial windows, in NHWC
+ // format.
+ WindowedDims ChooseWindowedDims();
+
+ std::mt19937& generator() { return *generator_; }
+
+ // Run the test case described by 'builder' with and without XLA and check
+ // that the outputs are close. Tensors x and y are close if they have the same
+ // type, same shape, and have close values. For floating-point tensors, the
+ // element-wise difference between x and y must no more than
+ // atol + rtol * abs(x); or both elements may be NaN or infinity. For
+ // non-floating-point tensors the element values must match exactly.
+ void ExpectTfAndXlaOutputsAreClose(const OpTestBuilder& builder,
+ double atol = 1e-2, double rtol = 1e-2);
+
+ protected:
+ // Per-test state:
+ std::unique_ptr<std::mt19937> generator_;
+
+ std::unique_ptr<Session> session_;
+
+ // Number of test cases built in 'session_'. Used to uniquify node names.
+ int num_tests_ = 0;
+};
+
+OpTest::OpTest() {
+ // Creates a random-number generator for the test case. Use the value of
+ // --tf_xla_random_seed as the seed, if provided.
+ int64 s = tf_xla_random_seed;
+ unsigned int seed;
+ if (s <= 0) {
+ std::random_device random_device;
+ seed = random_device();
+ } else {
+ seed = static_cast<unsigned int>(s);
+ }
+ LOG(INFO) << "Random seed for test case: " << seed
+ << ". To reproduce the "
+ "results of this test, pass flag --tf_xla_random_seed="
+ << seed;
+ generator_.reset(new std::mt19937(seed));
+
+ // Create a session with an empty graph.
+ SessionOptions session_options;
+ session_.reset(NewSession(session_options));
+ GraphDef def;
+ TF_CHECK_OK(session_->Create(def));
+}
+
+void OpTest::Repeatedly(std::function<void(void)> fn) {
+ int const max_repetitions = tf_xla_test_repetitions;
+ for (int i = 0; !HasFailure() && i < max_repetitions; ++i) {
+ fn();
+ }
+}
+
+template <typename T>
+T OpTest::Choose(gtl::ArraySlice<T> candidates) {
+ std::uniform_int_distribution<size_t> d(0, candidates.size() - 1);
+ return candidates[d(generator())];
+}
+
+int64 OpTest::RandomDim(int64 min, int64 max) {
+ std::uniform_int_distribution<int64> size_distribution(min, max - 1);
+ return size_distribution(generator());
+}
+
+std::vector<int64> OpTest::RandomDims(int min_rank, int max_rank,
+ int64 min_size, int64 max_size) {
+ CHECK_LE(0, min_rank);
+ CHECK_LE(min_rank, max_rank);
+ std::uniform_int_distribution<int> rank_distribution(min_rank, max_rank);
+ int rank = rank_distribution(generator());
+ std::vector<int64> dims(rank);
+ std::generate(dims.begin(), dims.end(), [this, min_size, max_size]() {
+ return RandomDim(min_size, max_size);
+ });
+ return dims;
+}
+
+Tensor OpTest::RandomTensor(DataType dtype, gtl::ArraySlice<int64> shape) {
+ Tensor tensor(dtype, TensorShape(shape));
+ switch (dtype) {
+ case DT_FLOAT: {
+ std::uniform_real_distribution<float> distribution(-1.0f, 1.0f);
+ test::FillFn<float>(&tensor, [this, &distribution](int i) -> float {
+ return distribution(generator());
+ });
+ break;
+ }
+ case DT_DOUBLE: {
+ std::uniform_real_distribution<double> distribution(-1.0, 1.0);
+ test::FillFn<double>(&tensor, [this, &distribution](int i) -> double {
+ return distribution(generator());
+ });
+ break;
+ }
+ case DT_INT32: {
+ std::uniform_int_distribution<int32> distribution(-(1 << 20), 1 << 20);
+ test::FillFn<int32>(&tensor, [this, &distribution](int i) -> int32 {
+ return distribution(generator());
+ });
+ break;
+ }
+ case DT_INT64: {
+ std::uniform_int_distribution<int64> distribution(-(1LL << 40),
+ 1LL << 40);
+ test::FillFn<int64>(&tensor, [this, &distribution](int i) -> int64 {
+ return distribution(generator());
+ });
+ break;
+ }
+ case DT_BOOL: {
+ std::bernoulli_distribution distribution;
+ test::FillFn<bool>(&tensor, [this, &distribution](int i) -> bool {
+ return distribution(generator());
+ });
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unimplemented type " << dtype << " in RandomTensor";
+ }
+ return tensor;
+}
+
+Tensor OpTest::RandomTensor(DataType dtype) {
+ return RandomTensor(dtype, RandomDims());
+}
+
+Tensor OpTest::RandomNonNegativeTensor(DataType dtype,
+ gtl::ArraySlice<int64> shape) {
+ Tensor tensor(dtype, TensorShape(shape));
+ switch (dtype) {
+ case DT_FLOAT: {
+ std::uniform_real_distribution<float> distribution(0.0f, 1.0f);
+ test::FillFn<float>(&tensor, [this, &distribution](int i) -> float {
+ return distribution(generator());
+ });
+ break;
+ }
+ case DT_DOUBLE: {
+ std::uniform_real_distribution<double> distribution(0.0, 1.0);
+ test::FillFn<double>(&tensor, [this, &distribution](int i) -> double {
+ return distribution(generator());
+ });
+ break;
+ }
+ case DT_INT32: {
+ std::uniform_int_distribution<int32> distribution(0, 1 << 20);
+ test::FillFn<int32>(&tensor, [this, &distribution](int i) -> int32 {
+ return distribution(generator());
+ });
+ break;
+ }
+ case DT_INT64: {
+ std::uniform_int_distribution<int64> distribution(0, 1LL << 40);
+ test::FillFn<int64>(&tensor, [this, &distribution](int i) -> int64 {
+ return distribution(generator());
+ });
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unimplemented type " << dtype
+ << " in RandomNonNegativeTensor";
+ }
+ return tensor;
+}
+
+Tensor OpTest::RandomNonNegativeTensor(DataType dtype) {
+ return RandomNonNegativeTensor(dtype, RandomDims());
+}
+
+std::pair<std::vector<int64>, std::vector<int64>> OpTest::BroadcastableDims(
+ std::vector<int64> dims) {
+ if (dims.empty()) return {dims, dims};
+
+ // Remove some dimensions from the front of 'dims'.
+ size_t skip =
+ std::uniform_int_distribution<size_t>(0, dims.size() - 1)(generator());
+
+ std::vector<int64> bdims(dims.begin() + skip, dims.end());
+
+ // Randomly replace some of the remaining dimensions of 'dims' with 1.
+ std::bernoulli_distribution random_bool;
+
+ for (int64& dim : bdims) {
+ if (random_bool(generator())) {
+ dim = 1LL;
+ }
+ }
+
+ // Possibly swap the roles of 'dims' and 'bdims'.
+ if (random_bool(generator())) {
+ dims.swap(bdims);
+ }
+ return {dims, bdims};
+}
+
+std::pair<std::vector<int64>, std::vector<int64>> OpTest::BroadcastableDims() {
+ return BroadcastableDims(RandomDims(0, 3));
+}
+
+Tensor OpTest::RandomReductionIndices(int rank) {
+ std::bernoulli_distribution random_bool;
+ std::vector<int32> indices;
+ for (int i = 0; i < rank; ++i) {
+ if (random_bool(generator())) {
+ indices.push_back(i);
+ }
+ }
+ return test::AsTensor<int32>(indices);
+}
+
+OpTest::WindowedDims OpTest::ChooseWindowedDims() {
+ WindowedDims d;
+ d.padding = Choose<Padding>({SAME, VALID});
+ std::uniform_int_distribution<int> random_int(1, 5);
+ Status s;
+ // Repeatedly try different filter/stride sizes until we find a valid
+ // combination.
+ do {
+ // CPU implementations require stride <= kernel size.
+ d.kernel_rows = random_int(generator()),
+ d.input_rows = RandomDim(d.kernel_rows);
+ d.stride_rows =
+ std::uniform_int_distribution<int>(1, d.kernel_rows)(generator());
+ int64 pad_dummy;
+ s = GetWindowedOutputSize(d.input_rows, d.kernel_rows, d.stride_rows,
+ d.padding, &d.output_rows, &pad_dummy);
+ } while (!s.ok());
+ do {
+ d.kernel_cols = random_int(generator());
+ d.input_cols = RandomDim(d.kernel_cols);
+ d.stride_cols =
+ std::uniform_int_distribution<int>(1, d.kernel_cols)(generator());
+ int64 pad_dummy;
+ s = GetWindowedOutputSize(d.input_cols, d.kernel_cols, d.stride_cols,
+ d.padding, &d.output_cols, &pad_dummy);
+ } while (!s.ok());
+ return d;
+}
+
+// Functions for comparing tensors.
+
+template <typename T>
+bool IsClose(const T& x, const T& y, double atol, double rtol) {
+ if (std::isnan(x) && std::isnan(y)) return true;
+ if (x == y) return true; // Allow inf == inf.
+ return fabs(x - y) < atol + rtol * fabs(x);
+}
+
+template <typename T>
+Status TensorsAreCloseImpl(const Tensor& x, const Tensor& y, double atol,
+ double rtol) {
+ auto Tx = x.flat<T>();
+ auto Ty = y.flat<T>();
+ for (int i = 0; i < Tx.size(); ++i) {
+ if (!IsClose(Tx(i), Ty(i), atol, rtol)) {
+ return errors::InvalidArgument(strings::StrCat(
+ i, "-th tensor element isn't close: ", Tx(i), " vs. ", Ty(i),
+ ". x = ", x.DebugString(), "y = ", y.DebugString(), "atol = ", atol,
+ " rtol = ", rtol, " tol = ", atol + rtol * std::fabs(Tx(i))));
+ }
+ }
+ return Status::OK();
+}
+
+template <typename T>
+Status TensorsAreEqualImpl(const Tensor& x, const Tensor& y) {
+ auto Tx = x.flat<T>();
+ auto Ty = y.flat<T>();
+ for (int i = 0; i < Tx.size(); ++i) {
+ if (Tx(i) != Ty(i)) {
+ return errors::InvalidArgument(strings::StrCat(
+ i, "-th tensor element isn't equal: ", Tx(i), " vs. ", Ty(i),
+ ". x = ", x.DebugString(), "y = ", y.DebugString()));
+ }
+ }
+ return Status::OK();
+}
+
+// Tests if "x" and "y" are tensors of the same type, same shape, and with
+// close values. For floating-point tensors, the element-wise difference between
+// x and y must no more than atol + rtol * abs(x). For non-floating-point
+// tensors the values must match exactly.
+Status TensorsAreClose(const Tensor& a, const Tensor& b, double atol,
+ double rtol) {
+ if (a.dtype() != b.dtype()) {
+ return errors::InvalidArgument(strings::StrCat(
+ "Tensors have different types: ", DataTypeString(a.dtype()), " and ",
+ DataTypeString(b.dtype())));
+ }
+ if (!a.IsSameSize(b)) {
+ return errors::InvalidArgument(strings::StrCat(
+ "Tensors have different shapes: ", a.shape().DebugString(), " and ",
+ b.shape().DebugString()));
+ }
+
+ switch (a.dtype()) {
+ case DT_FLOAT:
+ return TensorsAreCloseImpl<float>(a, b, atol, rtol);
+ case DT_DOUBLE:
+ return TensorsAreCloseImpl<double>(a, b, atol, rtol);
+ case DT_INT32:
+ return TensorsAreEqualImpl<int32>(a, b);
+ case DT_INT64:
+ return TensorsAreEqualImpl<int64>(a, b);
+ case DT_BOOL:
+ return TensorsAreEqualImpl<bool>(a, b);
+ default:
+ LOG(FATAL) << "Unexpected type : " << DataTypeString(a.dtype());
+ }
+}
+
+void OpTest::ExpectTfAndXlaOutputsAreClose(const OpTestBuilder& builder,
+ double atol, double rtol) {
+ string cpu_device = DeviceTypeToDeviceName(DEVICE_CPU);
+ DeviceType test_device_type(*tf_xla_test_device_ptr);
+ string test_device = DeviceTypeToDeviceName(test_device_type);
+ ++num_tests_;
+
+ GraphDef graph;
+ std::vector<string> expected_inputs, test_inputs;
+ std::vector<string> expected_fetches, test_fetches;
+ TF_ASSERT_OK(builder.BuildGraph(
+ strings::StrCat("test", num_tests_, "_expected"), cpu_device,
+ /* use_jit= */ false, &graph, /* test_node_def= */ nullptr,
+ &expected_inputs, &expected_fetches));
+
+ NodeDef* node_def;
+ TF_ASSERT_OK(builder.BuildGraph(strings::StrCat("test", num_tests_, "_test"),
+ test_device, tf_xla_test_use_jit, &graph,
+ &node_def, &test_inputs, &test_fetches));
+
+ // Check that there's a kernel corresponding to 'node_def' on the device under
+ // test.
+ Status status = FindKernelDef(test_device_type, *node_def, nullptr, nullptr);
+ if (!status.ok()) {
+ VLOG(1) << "Skipping test because there is no corresponding registered "
+ << "kernel on the test device: " << status;
+ return;
+ }
+
+ TF_ASSERT_OK(session_->Extend(graph));
+
+ const std::vector<Tensor>& input_tensors = builder.inputs();
+ if (VLOG_IS_ON(1)) {
+ for (const Tensor& input : input_tensors) {
+ VLOG(1) << "Input: " << input.DebugString();
+ }
+ }
+
+ std::vector<std::pair<string, Tensor>> expected_feeds(expected_inputs.size());
+ std::vector<std::pair<string, Tensor>> test_feeds(test_inputs.size());
+ ASSERT_EQ(input_tensors.size(), expected_inputs.size());
+ ASSERT_EQ(input_tensors.size(), test_inputs.size());
+
+ for (int i = 0; i < input_tensors.size(); ++i) {
+ expected_feeds[i] = {expected_inputs[i], input_tensors[i]};
+ test_feeds[i] = {test_inputs[i], input_tensors[i]};
+ }
+
+ std::vector<Tensor> expected_outputs, test_outputs;
+ VLOG(1) << "Running expected graph";
+ Status s =
+ session_->Run(expected_feeds, expected_fetches, {}, &expected_outputs);
+ if (!s.ok()) {
+ VLOG(1) << "Expected graph failed with status: " << s << ". Skipping test";
+ return;
+ }
+
+ VLOG(1) << "Running test graph";
+ TF_ASSERT_OK(session_->Run(test_feeds, test_fetches, {}, &test_outputs));
+
+ ASSERT_EQ(expected_outputs.size(), test_outputs.size());
+ for (int j = 0; s.ok() && j < test_outputs.size(); ++j) {
+ s = TensorsAreClose(expected_outputs[j], test_outputs[j], atol, rtol);
+ }
+ TF_EXPECT_OK(s);
+}
+
+// Helper that converts 'values' to an int32 or int64 Tensor.
+Tensor AsIntTensor(DataType dtype, const std::vector<int64>& values) {
+ switch (dtype) {
+ case DT_INT32: {
+ std::vector<int32> values32(values.begin(), values.end());
+ return test::AsTensor<int32>(values32);
+ }
+ case DT_INT64:
+ return test::AsTensor<int64>(values);
+ default:
+ CHECK(false);
+ }
+}
+
+TEST_F(OpTest, Abs) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Abs").Input(RandomTensor(type)).Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Add) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Add")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, AddN) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ int n = std::uniform_int_distribution<int>(1, 5)(generator());
+
+ auto shape = RandomDims();
+
+ OpTestBuilder builder("AddN");
+ builder.Attr("T", type);
+ builder.Attr("N", n);
+ for (int i = 0; i < n; ++i) {
+ builder.Input(RandomTensor(type, shape));
+ }
+ ExpectTfAndXlaOutputsAreClose(builder);
+ });
+}
+
+TEST_F(OpTest, All) {
+ Repeatedly([this]() {
+ Tensor data = RandomTensor(DT_BOOL);
+ Tensor indices = RandomReductionIndices(data.dims());
+ bool keep_dims = Choose<bool>({false, true});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("All").Input(data).Input(indices).Attr("keep_dims",
+ keep_dims));
+ });
+}
+
+TEST_F(OpTest, Any) {
+ Repeatedly([this]() {
+ Tensor data = RandomTensor(DT_BOOL);
+ Tensor indices = RandomReductionIndices(data.dims());
+ bool keep_dims = Choose<bool>({false, true});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Any").Input(data).Input(indices).Attr("keep_dims",
+ keep_dims));
+ });
+}
+
+TEST_F(OpTest, AvgPool) {
+ Repeatedly([this]() {
+ std::uniform_int_distribution<int> random_int(1, 5);
+ int kernel_rows = random_int(generator()),
+ kernel_cols = random_int(generator());
+ int stride_rows = random_int(generator()),
+ stride_cols = random_int(generator());
+ string padding = Choose<string>({"SAME", "VALID"});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("AvgPool")
+ .Input(
+ RandomTensor(DT_FLOAT, {RandomDim(1), RandomDim(kernel_rows),
+ RandomDim(kernel_cols), RandomDim(1)}))
+ .Attr("T", DT_FLOAT)
+ .Attr("ksize", {1, kernel_rows, kernel_cols, 1})
+ .Attr("strides", {1, stride_rows, stride_cols, 1})
+ .Attr("padding", padding)
+ .Attr("data_format", "NHWC"));
+ });
+ // TODO(phawkins): the CPU device only implements spatial pooling. Add tests
+ // for batch pooling when supported.
+}
+
+TEST_F(OpTest, AvgPoolGrad) {
+ Repeatedly([this]() {
+ int batch = RandomDim(1), features = RandomDim(1);
+ WindowedDims d = ChooseWindowedDims();
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("AvgPoolGrad")
+ .Input(test::AsTensor<int32>(
+ {batch, d.input_rows, d.input_cols, features}))
+ .Input(RandomTensor(
+ DT_FLOAT, {batch, d.output_rows, d.output_cols, features}))
+ .Attr("T", DT_FLOAT)
+ .Attr("ksize", {1, d.kernel_rows, d.kernel_cols, 1})
+ .Attr("strides", {1, d.stride_rows, d.stride_cols, 1})
+ .Attr("padding", d.padding == SAME ? "SAME" : "VALID")
+ .Attr("data_format", "NHWC"));
+ });
+}
+
+TEST_F(OpTest, BatchMatMul) {
+ Repeatedly([this]() {
+ std::vector<int64> output_dims = RandomDims(2, 5, 0, 7);
+ int64 ndims = output_dims.size();
+ int64 inner_dim = RandomDim();
+ std::vector<int64> x_dims(output_dims), y_dims(output_dims);
+ x_dims[ndims - 1] = inner_dim;
+ y_dims[ndims - 2] = inner_dim;
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("BatchMatMul")
+ .Input(RandomTensor(DT_FLOAT, x_dims))
+ .Input(RandomTensor(DT_FLOAT, y_dims))
+ .Attr("T", DT_FLOAT));
+
+ std::swap(x_dims[ndims - 1], x_dims[ndims - 2]);
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("BatchMatMul")
+ .Input(RandomTensor(DT_FLOAT, x_dims))
+ .Input(RandomTensor(DT_FLOAT, y_dims))
+ .Attr("T", DT_FLOAT)
+ .Attr("adj_x", true));
+
+ std::swap(y_dims[ndims - 1], y_dims[ndims - 2]);
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("BatchMatMul")
+ .Input(RandomTensor(DT_FLOAT, x_dims))
+ .Input(RandomTensor(DT_FLOAT, y_dims))
+ .Attr("T", DT_FLOAT)
+ .Attr("adj_x", true)
+ .Attr("adj_y", true));
+
+ std::swap(x_dims[ndims - 1], x_dims[ndims - 2]);
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("BatchMatMul")
+ .Input(RandomTensor(DT_FLOAT, x_dims))
+ .Input(RandomTensor(DT_FLOAT, y_dims))
+ .Attr("T", DT_FLOAT)
+ .Attr("adj_y", true));
+ });
+}
+
+TEST_F(OpTest, BiasAdd) {
+ Repeatedly([this]() {
+ auto x = RandomTensor(DT_FLOAT, RandomDims(2, kDefaultMaxRank));
+ auto y = RandomTensor(DT_FLOAT, {x.dim_size(x.dims() - 1)});
+ // TODO(phawkins): test both data formats.
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("BiasAdd").Input(x).Input(y).Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, BiasAddGrad) {
+ Repeatedly([this]() {
+ auto x = RandomTensor(DT_FLOAT);
+ // TODO(phawkins): test both data formats.
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("BiasAddGrad").Input(x).Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, BiasAddV1) {
+ Repeatedly([this]() {
+ auto x = RandomTensor(DT_FLOAT, RandomDims(2, kDefaultMaxRank));
+ auto y = RandomTensor(DT_FLOAT, {x.dim_size(x.dims() - 1)});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("BiasAddV1").Input(x).Input(y).Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, BroadcastGradientArgs) {
+ Repeatedly([this]() {
+ // TODO(phawkins): only int32 seems to be implemented in Tensorflow.
+ // DataType type = Choose<DataType>({DT_INT32, DT_INT64});
+ DataType type = DT_INT32;
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("BroadcastGradientArgs")
+ .Input(AsIntTensor(type, dims.first))
+ .Input(AsIntTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Cast) {
+ Repeatedly([this]() {
+ DataType src_type, dst_type;
+ src_type = Choose<DataType>({DT_INT32, DT_FLOAT, DT_BOOL});
+ dst_type = Choose<DataType>({DT_INT32, DT_FLOAT, DT_BOOL});
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Cast")
+ .Input(RandomTensor(src_type))
+ .Attr("SrcT", src_type)
+ .Attr("DstT", dst_type));
+ });
+}
+
+TEST_F(OpTest, Ceil) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Ceil")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Concat) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ int n = std::uniform_int_distribution<int>(2, 5)(generator());
+
+ std::vector<int64> dims = RandomDims(1);
+ int concat_dim =
+ std::uniform_int_distribution<int32>(0, dims.size() - 1)(generator());
+
+ OpTestBuilder builder("Concat");
+ builder.Input(test::AsScalar<int32>(concat_dim));
+ builder.Attr("T", type);
+ builder.Attr("N", n);
+ for (int i = 0; i < n; ++i) {
+ std::vector<int64> shape = dims;
+ shape[concat_dim] = RandomDim();
+ builder.Input(RandomTensor(type, shape));
+ }
+ ExpectTfAndXlaOutputsAreClose(builder);
+ });
+}
+
+TEST_F(OpTest, ConcatOffset) {
+ Repeatedly([this]() {
+ int n = std::uniform_int_distribution<int>(2, 5)(generator());
+
+ std::vector<int64> dims = RandomDims(1);
+ int concat_dim =
+ std::uniform_int_distribution<int32>(0, dims.size() - 1)(generator());
+
+ OpTestBuilder builder("ConcatOffset");
+ builder.Input(test::AsScalar<int32>(concat_dim));
+ builder.Attr("N", n);
+ for (int i = 0; i < n; ++i) {
+ std::vector<int32> shape(dims.begin(), dims.end());
+ shape[concat_dim] = RandomDim();
+ builder.Input(test::AsTensor<int32>(shape));
+ }
+ ExpectTfAndXlaOutputsAreClose(builder);
+ });
+}
+
+TEST_F(OpTest, Conv2D) {
+ Repeatedly([this]() {
+ WindowedDims d = ChooseWindowedDims();
+ std::uniform_int_distribution<int> random_int(1, 5);
+ int features_in = random_int(generator());
+ int features_out = random_int(generator());
+ Tensor data = RandomTensor(
+ DT_FLOAT, {RandomDim(), d.input_rows, d.input_cols, features_in});
+
+ Tensor kernel = RandomTensor(
+ DT_FLOAT, {d.kernel_rows, d.kernel_cols, features_in, features_out});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Conv2D")
+ .Input(data)
+ .Input(kernel)
+ .Attr("T", DT_FLOAT)
+ .Attr("strides", {1, d.stride_rows, d.stride_cols, 1})
+ .Attr("padding", d.padding == SAME ? "SAME" : "VALID")
+ .Attr("data_format", "NHWC"));
+ });
+}
+
+TEST_F(OpTest, Conv2DBackpropFilter) {
+ Repeatedly([this]() {
+ WindowedDims d = ChooseWindowedDims();
+ std::uniform_int_distribution<int> random_int(1, 5);
+ int features_in = random_int(generator());
+ int features_out = random_int(generator());
+ int32 batch = RandomDim();
+ Tensor activations = RandomTensor(
+ DT_FLOAT, {batch, d.input_rows, d.input_cols, features_in});
+ Tensor backprop = RandomTensor(
+ DT_FLOAT, {batch, d.output_rows, d.output_cols, features_out});
+ Tensor kernel_shape = test::AsTensor<int32>(
+ {d.kernel_rows, d.kernel_cols, features_in, features_out});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Conv2DBackpropFilter")
+ .Input(activations)
+ .Input(kernel_shape)
+ .Input(backprop)
+ .Attr("T", DT_FLOAT)
+ .Attr("strides", {1, d.stride_rows, d.stride_cols, 1})
+ .Attr("padding", d.padding == SAME ? "SAME" : "VALID")
+ .Attr("data_format", "NHWC"));
+ });
+}
+
+TEST_F(OpTest, Conv2DBackpropInput) {
+ Repeatedly([this]() {
+ WindowedDims d = ChooseWindowedDims();
+ std::uniform_int_distribution<int> random_int(1, 5);
+ int features_in = random_int(generator());
+ int features_out = random_int(generator());
+ int32 batch = RandomDim();
+ Tensor in_shape =
+ test::AsTensor<int32>({batch, d.input_rows, d.input_cols, features_in});
+ Tensor backprop = RandomTensor(
+ DT_FLOAT, {batch, d.output_rows, d.output_cols, features_out});
+ Tensor kernel = RandomTensor(
+ DT_FLOAT, {d.kernel_rows, d.kernel_cols, features_in, features_out});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Conv2DBackpropInput")
+ .Input(in_shape)
+ .Input(kernel)
+ .Input(backprop)
+ .Attr("T", DT_FLOAT)
+ .Attr("strides", {1, d.stride_rows, d.stride_cols, 1})
+ .Attr("padding", d.padding == SAME ? "SAME" : "VALID")
+ .Attr("data_format", "NHWC"));
+ });
+}
+
+TEST_F(OpTest, Diag) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Diag")
+ .Input(RandomTensor(type, RandomDims(1)))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, DiagPart) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = RandomDims(1, 3);
+ // Duplicate the random dims.
+ std::vector<int64> doubled_dims(dims.size() * 2);
+ std::copy(dims.begin(), dims.end(), doubled_dims.begin());
+ std::copy(dims.begin(), dims.end(), doubled_dims.begin() + dims.size());
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("DiagPart")
+ .Input(RandomTensor(type, doubled_dims))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Div) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Div")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, DynamicStitch) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ int n = std::uniform_int_distribution<int>(2, 5)(generator());
+ OpTestBuilder builder("DynamicStitch");
+ builder.Attr("T", type);
+ builder.Attr("N", n);
+ std::vector<std::vector<int64>> index_dims;
+ int size = 0;
+ // TODO(phawkins): the XLA implementation of DynamicStitch does not
+ // accept an empty set of indices.
+ do {
+ size = 0;
+ index_dims.clear();
+ for (int i = 0; i < n; ++i) {
+ std::vector<int64> dims = RandomDims(0, 3, 0, 5);
+ size += TensorShape(dims).num_elements();
+ index_dims.push_back(dims);
+ }
+ } while (size == 0);
+
+ // Shuffle the range of indices that cover the output.
+ // TODO(phawkins): The documentation for DynamicStitch doesn't require that
+ // the indices cover all positions of the output. The XLA implementation
+ // does so require. However, the native TF implementation leaves undefined
+ // values if we don't cover everything, so we can't really test that case
+ // anyway.
+ std::vector<int32> indices(size);
+ std::iota(indices.begin(), indices.end(), 0);
+ std::shuffle(indices.begin(), indices.end(), generator());
+
+ int pos = 0;
+ for (int i = 0; i < n; ++i) {
+ TensorShape shape(index_dims[i]);
+ Tensor t = test::AsTensor<int32>(
+ gtl::ArraySlice<int32>(indices, pos, shape.num_elements()), shape);
+ builder.Input(t);
+ pos += t.NumElements();
+ }
+
+ std::vector<int64> constant_dims = RandomDims(0, 3, 0, 5);
+ for (int i = 0; i < n; ++i) {
+ std::vector<int64> dims(index_dims[i].begin(), index_dims[i].end());
+ std::copy(constant_dims.begin(), constant_dims.end(),
+ std::back_inserter(dims));
+ Tensor t = RandomTensor(type, dims);
+ builder.Input(t);
+ }
+ ExpectTfAndXlaOutputsAreClose(builder);
+ });
+}
+
+TEST_F(OpTest, Equal) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Equal")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Exp) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Exp").Input(RandomTensor(DT_FLOAT)).Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, ExpandDims) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ Tensor in = RandomTensor(type);
+ Tensor dim(DT_INT32, TensorShape());
+ std::uniform_int_distribution<int32> d(-1 - in.dims(), in.dims());
+ dim.scalar<int32>()() = d(generator());
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("ExpandDims").Input(in).Input(dim).Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Fill) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ Tensor scalar = RandomTensor(type, {});
+ std::vector<int64> dims = RandomDims();
+ std::vector<int32> shape(dims.begin(), dims.end());
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Fill")
+ .Input(test::AsTensor<int32>(shape))
+ .Input(scalar)
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Floor) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Floor")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, FloorDiv) {
+ Repeatedly([this]() {
+ DataType type = DT_INT32;
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("FloorDiv")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, FloorMod) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("FloorMod")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Greater) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Greater")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, GreaterEqual) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("GreaterEqual")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Reciprocal) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Reciprocal")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, L2Loss) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ // TODO(b/31644876): scalars currently crash.
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("L2Loss")
+ .Input(RandomTensor(type, RandomDims(1)))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Less) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Less")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, LessEqual) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("LessEqual")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, LinSpace) {
+ Repeatedly([this]() {
+ auto ToScalar = [](DataType type, int x) {
+ if (type == DT_INT32) return test::AsScalar<int32>(x);
+ return test::AsScalar<int64>(x);
+ };
+ std::uniform_int_distribution<int> distribution(-50, 50);
+ DataType type = Choose<DataType>({DT_INT32, DT_INT64});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("LinSpace")
+ .Input(RandomTensor(DT_FLOAT, {}))
+ .Input(RandomTensor(DT_FLOAT, {}))
+ .Input(ToScalar(type, distribution(generator())))
+ .Attr("T", DT_FLOAT)
+ .Attr("Tidx", type));
+ });
+}
+
+TEST_F(OpTest, Log) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Log").Input(RandomTensor(DT_FLOAT)).Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, LogicalAnd) {
+ Repeatedly([this]() {
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("LogicalAnd")
+ .Input(RandomTensor(DT_BOOL, dims.first))
+ .Input(RandomTensor(DT_BOOL, dims.second)));
+ });
+}
+
+TEST_F(OpTest, LogicalNot) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("LogicalNot").Input(RandomTensor(DT_BOOL)));
+ });
+}
+
+TEST_F(OpTest, LogicalOr) {
+ Repeatedly([this]() {
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("LogicalOr")
+ .Input(RandomTensor(DT_BOOL, dims.first))
+ .Input(RandomTensor(DT_BOOL, dims.second)));
+ });
+}
+
+TEST_F(OpTest, LogSoftmax) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("LogSoftmax")
+ .Input(RandomTensor(DT_FLOAT, RandomDims(2, 2)))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, LRN) {
+ Repeatedly([this]() {
+ Tensor data;
+ // TODO(b/31362467): Crashes with 0 dims on GPU. Re-enable when fixed.
+ data = RandomTensor(DT_FLOAT, RandomDims(4, 4, 1, 8));
+ // CuDNN requires depth_radius > 0.
+ std::uniform_int_distribution<int> radius(1, data.dim_size(3));
+ std::uniform_real_distribution<float> coeff(0.01, 2.0);
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("LRN")
+ .Input(data)
+ .Attr("T", DT_FLOAT)
+ .Attr("depth_radius", radius(generator()))
+ .Attr("bias", coeff(generator()))
+ .Attr("alpha", coeff(generator()))
+ .Attr("beta", coeff(generator())));
+ });
+}
+
+TEST_F(OpTest, LRNGrad) {
+ Repeatedly([this]() {
+ // TODO(b/31362467): Crashes with 0 dims on GPU. Re-enable when fixed.
+ std::vector<int64> dims = RandomDims(4, 4, 1, 8);
+ Tensor input_grads = RandomTensor(DT_FLOAT, dims);
+ Tensor input_image = RandomTensor(DT_FLOAT, dims);
+ Tensor output_image = RandomTensor(DT_FLOAT, dims);
+ // CuDNN requires depth_radius > 0.
+ std::uniform_int_distribution<int> radius(1, input_grads.dim_size(3));
+ std::uniform_real_distribution<float> coeff(0.0, 2.0);
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("LRNGrad")
+ .Input(input_grads)
+ .Input(input_image)
+ .Input(output_image)
+ .Attr("T", DT_FLOAT)
+ .Attr("depth_radius", radius(generator()))
+ .Attr("bias", coeff(generator()))
+ .Attr("alpha", coeff(generator()))
+ .Attr("beta", coeff(generator())));
+ });
+}
+
+TEST_F(OpTest, MatMul) {
+ Repeatedly([this]() {
+ int64 x = RandomDim();
+ int64 y = RandomDim();
+ int64 z = RandomDim();
+
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("MatMul")
+ .Input(RandomTensor(DT_FLOAT, {x, y}))
+ .Input(RandomTensor(DT_FLOAT, {y, z}))
+ .Attr("T", DT_FLOAT));
+
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("MatMul")
+ .Input(RandomTensor(DT_FLOAT, {y, x}))
+ .Input(RandomTensor(DT_FLOAT, {y, z}))
+ .Attr("T", DT_FLOAT)
+ .Attr("transpose_a", true));
+
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("MatMul")
+ .Input(RandomTensor(DT_FLOAT, {x, y}))
+ .Input(RandomTensor(DT_FLOAT, {z, y}))
+ .Attr("T", DT_FLOAT)
+ .Attr("transpose_b", true));
+
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("MatMul")
+ .Input(RandomTensor(DT_FLOAT, {y, x}))
+ .Input(RandomTensor(DT_FLOAT, {z, y}))
+ .Attr("T", DT_FLOAT)
+ .Attr("transpose_a", true)
+ .Attr("transpose_b", true));
+ });
+}
+
+TEST_F(OpTest, MatrixDiag) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_BOOL, DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("MatrixDiag")
+ .Input(RandomTensor(type, RandomDims(1)))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, MatrixDiagPart) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_BOOL, DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("MatrixDiagPart")
+ .Input(RandomTensor(type, RandomDims(2)))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Max) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ Tensor data = RandomTensor(type);
+ Tensor indices = RandomReductionIndices(data.dims());
+ bool keep_dims = Choose<bool>({false, true});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Max").Input(data).Input(indices).Attr("T", type).Attr(
+ "keep_dims", keep_dims));
+ });
+}
+
+TEST_F(OpTest, Maximum) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Maximum")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, MaxPool) {
+ Repeatedly([this]() {
+ std::uniform_int_distribution<int> random_int(1, 5);
+ int kernel_rows = random_int(generator()),
+ kernel_cols = random_int(generator());
+ int stride_rows = random_int(generator()),
+ stride_cols = random_int(generator());
+ string padding = Choose<string>({"SAME", "VALID"});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("MaxPool")
+ .Input(
+ RandomTensor(DT_FLOAT, {RandomDim(1), RandomDim(kernel_rows),
+ RandomDim(kernel_cols), RandomDim(1)}))
+ .Attr("T", DT_FLOAT)
+ .Attr("ksize", {1, kernel_rows, kernel_cols, 1})
+ .Attr("strides", {1, stride_rows, stride_cols, 1})
+ .Attr("padding", padding)
+ .Attr("data_format", "NHWC"));
+ });
+ // TODO(phawkins): test NCHW format (not supported by CPU)
+}
+
+TEST_F(OpTest, Mean) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ // TODO(phawkins): CPU and XLA differ output for reducing across a
+ // size-0 dimension (nan vs 0). For now, require size >= 1.
+ Tensor data = RandomTensor(type, RandomDims(0, kDefaultMaxRank, 1));
+ Tensor indices = RandomReductionIndices(data.dims());
+ bool keep_dims = Choose<bool>({false, true});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Mean").Input(data).Input(indices).Attr("T", type).Attr(
+ "keep_dims", keep_dims));
+ });
+}
+
+TEST_F(OpTest, Min) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ Tensor data = RandomTensor(type);
+ Tensor indices = RandomReductionIndices(data.dims());
+ bool keep_dims = Choose<bool>({false, true});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Min").Input(data).Input(indices).Attr("T", type).Attr(
+ "keep_dims", keep_dims));
+ });
+}
+
+TEST_F(OpTest, Minimum) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Minimum")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Mod) {
+ Repeatedly([this]() {
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Mod")
+ .Input(RandomTensor(DT_INT32, dims.first))
+ .Input(RandomTensor(DT_INT32, dims.second))
+ .Attr("T", DT_INT32));
+ });
+}
+
+TEST_F(OpTest, Mul) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Mul")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Neg) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Neg").Input(RandomTensor(type)).Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, NotEqual) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("NotEqual")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Pack) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ int n = std::uniform_int_distribution<int>(1, 5)(generator());
+
+ std::vector<int64> dims = RandomDims();
+ int num_dims = dims.size();
+ int axis = std::uniform_int_distribution<int32>(-num_dims - 1,
+ num_dims)(generator());
+
+ OpTestBuilder builder("Pack");
+ builder.Attr("T", type);
+ builder.Attr("N", n);
+ builder.Attr("axis", axis);
+ for (int i = 0; i < n; ++i) {
+ builder.Input(RandomTensor(type, dims));
+ }
+ ExpectTfAndXlaOutputsAreClose(builder);
+ });
+}
+
+// TODO(b/31741898): crashes on GPU.
+TEST_F(OpTest, Pad) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ Tensor t = RandomTensor(type);
+
+ // TODO(b/31741996): re-enable DT_INT64 when bug is fixed.
+ // DataType tpaddings = Choose<DataType>({DT_INT32, DT_INT64});
+ DataType tpaddings = DT_INT32;
+ std::vector<int64> paddings_vec;
+ std::uniform_int_distribution<int> distribution(0, 7);
+ for (int i = 0; i < t.dims(); ++i) {
+ paddings_vec.push_back(distribution(generator()));
+ paddings_vec.push_back(distribution(generator()));
+ }
+ Tensor paddings;
+ CHECK(paddings.CopyFrom(AsIntTensor(tpaddings, paddings_vec),
+ TensorShape({t.dims(), 2})));
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Pad").Input(t).Input(paddings).Attr("T", type).Attr(
+ "Tpaddings", tpaddings));
+ });
+}
+
+TEST_F(OpTest, Pow) {
+ // TODO(phawkins): Feeding large DT_INT32 values to Pow() leads to
+ // nontermination.
+ Repeatedly([this]() {
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Pow")
+ .Input(RandomTensor(DT_FLOAT, dims.first))
+ .Input(RandomTensor(DT_FLOAT, dims.second))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Prod) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ Tensor data = RandomTensor(type);
+ Tensor indices = RandomReductionIndices(data.dims());
+ bool keep_dims = Choose<bool>({false, true});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Prod").Input(data).Input(indices).Attr("T", type).Attr(
+ "keep_dims", keep_dims));
+ });
+}
+
+TEST_F(OpTest, Range) {
+ Repeatedly([this]() {
+ auto ToScalar = [](DataType type, int x) {
+ if (type == DT_INT32) return test::AsScalar<int32>(x);
+ if (type == DT_INT64) return test::AsScalar<int64>(x);
+ if (type == DT_FLOAT) return test::AsScalar<float>(x);
+ if (type == DT_DOUBLE) return test::AsScalar<double>(x);
+ LOG(FATAL) << "Unknown type " << DataTypeString(type);
+ };
+ std::uniform_int_distribution<int> distribution(-50, 50);
+ DataType tidx = Choose<DataType>({DT_INT32, DT_INT64, DT_FLOAT, DT_DOUBLE});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Range")
+ .Input(ToScalar(tidx, distribution(generator())))
+ .Input(ToScalar(tidx, distribution(generator())))
+ .Input(ToScalar(tidx, distribution(generator())))
+ .Attr("Tidx", tidx));
+ });
+}
+
+TEST_F(OpTest, Rank) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Rank").Input(RandomTensor(type)).Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, RealDiv) {
+ Repeatedly([this]() {
+ DataType type = DT_FLOAT;
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("RealDiv")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Relu) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Relu")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Relu6) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Relu6")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Relu6Grad) {
+ Repeatedly([this]() {
+ auto dims = RandomDims(1);
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Relu6Grad")
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, ReluGrad) {
+ Repeatedly([this]() {
+ auto dims = RandomDims(1);
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("ReluGrad")
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Reshape) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ std::vector<int64> dims = RandomDims();
+ std::bernoulli_distribution random_bool;
+ std::vector<int64> dims_before, dims_after;
+ for (std::vector<int64>* out : {&dims_before, &dims_after}) {
+ std::shuffle(dims.begin(), dims.end(), generator());
+ for (int64 dim : dims) {
+ // Either add the dimension as a new dimension or merge it with the
+ // previous dimension.
+ if (out->empty() || random_bool(generator())) {
+ out->push_back(dim);
+ } else {
+ out->back() *= dim;
+ }
+ }
+ }
+ Tensor data = RandomTensor(type, dims_before);
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Reshape")
+ .Input(data)
+ .Input(test::AsTensor<int32>(
+ std::vector<int32>(dims_after.begin(), dims_after.end())))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Rsqrt) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Rsqrt")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, RsqrtGrad) {
+ Repeatedly([this]() {
+ auto dims = RandomDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("RsqrtGrad")
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Shape) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Shape").Input(RandomTensor(type)).Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, ShapeN) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ int n = std::uniform_int_distribution<int>(1, 5)(generator());
+ OpTestBuilder builder("ShapeN");
+ builder.Attr("T", type);
+ builder.Attr("N", n);
+ for (int i = 0; i < n; ++i) {
+ builder.Input(RandomTensor(type));
+ }
+ ExpectTfAndXlaOutputsAreClose(builder);
+ });
+}
+
+TEST_F(OpTest, Sigmoid) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Sigmoid")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, SigmoidGrad) {
+ Repeatedly([this]() {
+ auto dims = RandomDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("SigmoidGrad")
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Sign) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Sign").Input(RandomTensor(type)).Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Size) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Size").Input(RandomTensor(type)).Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Slice) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ Tensor data = RandomTensor(type);
+
+ std::vector<int32> begin(data.dims()), size(data.dims());
+ for (int i = 0; i < data.dims(); ++i) {
+ begin[i] = std::uniform_int_distribution<int32>(
+ 0, data.dim_size(i))(generator());
+ size[i] = std::uniform_int_distribution<int32>(
+ -1, data.dim_size(i) - begin[i])(generator());
+ }
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Slice")
+ .Input(data)
+ .Input(test::AsTensor<int32>(begin))
+ .Input(test::AsTensor<int32>(size))
+ .Attr("T", type)
+ .Attr("Index", DT_INT32));
+ });
+}
+
+TEST_F(OpTest, Softmax) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Softmax")
+ .Input(RandomTensor(DT_FLOAT, RandomDims(2, 2)))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Split) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ std::vector<int64> dims = RandomDims(1);
+ std::uniform_int_distribution<int> ud;
+ int32 dim = std::uniform_int_distribution<int32>(
+ 0, static_cast<int32>(dims.size()) - 1)(generator());
+ int n = std::uniform_int_distribution<int>(1, 5)(generator());
+ // Ensure 'dim' is evenly divisible by 'n'.
+ dims[dim] /= n;
+ dims[dim] *= n;
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Split")
+ .Input(test::AsScalar<int32>(dim))
+ .Input(RandomTensor(type, dims))
+ .Attr("T", type)
+ .Attr("num_split", n));
+ });
+}
+
+TEST_F(OpTest, Softplus) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Softplus")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, SoftplusGrad) {
+ Repeatedly([this]() {
+ std::vector<int64> dims = RandomDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("SoftplusGrad")
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, SparseMatMul) {
+ Repeatedly([this]() {
+ int64 x = RandomDim();
+ int64 y = RandomDim();
+ int64 z = RandomDim();
+
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("SparseMatMul")
+ .Input(RandomTensor(DT_FLOAT, {x, y}))
+ .Input(RandomTensor(DT_FLOAT, {y, z}))
+ .Attr("Ta", DT_FLOAT)
+ .Attr("Tb", DT_FLOAT));
+
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("SparseMatMul")
+ .Input(RandomTensor(DT_FLOAT, {y, x}))
+ .Input(RandomTensor(DT_FLOAT, {y, z}))
+ .Attr("Ta", DT_FLOAT)
+ .Attr("Tb", DT_FLOAT)
+ .Attr("transpose_a", true));
+
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("SparseMatMul")
+ .Input(RandomTensor(DT_FLOAT, {x, y}))
+ .Input(RandomTensor(DT_FLOAT, {z, y}))
+ .Attr("Ta", DT_FLOAT)
+ .Attr("Tb", DT_FLOAT)
+ .Attr("transpose_b", true));
+
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("SparseMatMul")
+ .Input(RandomTensor(DT_FLOAT, {y, x}))
+ .Input(RandomTensor(DT_FLOAT, {z, y}))
+ .Attr("Ta", DT_FLOAT)
+ .Attr("Tb", DT_FLOAT)
+ .Attr("transpose_a", true)
+ .Attr("transpose_b", true));
+ });
+}
+
+TEST_F(OpTest, Sqrt) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Sqrt")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, SquaredDifference) {
+ Repeatedly([this]() {
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("SquaredDifference")
+ .Input(RandomTensor(DT_FLOAT, dims.first))
+ .Input(RandomTensor(DT_FLOAT, dims.second))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Square) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Square").Input(RandomTensor(type)).Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Squeeze) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ Tensor t = RandomTensor(type, RandomDims(0, kDefaultMaxRank, 0, 5));
+ std::bernoulli_distribution random_bool;
+ std::vector<int> squeeze_dims;
+ for (int i = 0; i < t.dims(); ++i) {
+ if (t.dim_size(i) == 1 && random_bool(generator())) {
+ squeeze_dims.push_back(i);
+ }
+ }
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Squeeze")
+ .Input(t)
+ .Attr("squeeze_dims", squeeze_dims)
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Sub) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Sub")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Sum) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ Tensor data = RandomTensor(type);
+ Tensor indices = RandomReductionIndices(data.dims());
+ bool keep_dims = Choose<bool>({false, true});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("Sum").Input(data).Input(indices).Attr("T", type).Attr(
+ "keep_dims", keep_dims));
+ });
+}
+
+TEST_F(OpTest, StridedSlice) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ Tensor data = RandomTensor(type);
+
+ std::vector<int32> begin(data.dims()), end(data.dims());
+ std::vector<int32> strides(data.dims());
+ for (int i = 0; i < data.dims(); ++i) {
+ begin[i] = std::uniform_int_distribution<int32>(
+ -2 * data.dim_size(i), 2 * data.dim_size(i))(generator());
+ end[i] = std::uniform_int_distribution<int32>(
+ -2 * data.dim_size(i), 2 * data.dim_size(i))(generator());
+ // TODO(b/31360685): support strides other than 1 or -1
+ strides[i] = std::bernoulli_distribution()(generator()) ? 1 : -1;
+ }
+ int64 max_bitmask = (1LL << data.dims()) - 1;
+ std::uniform_int_distribution<int64> bitmask_distribution(0, max_bitmask);
+ int64 begin_mask = bitmask_distribution(generator());
+ int64 end_mask = bitmask_distribution(generator());
+
+ // Create a ellipsis bitmask with at most one 1 bit set.
+ int64 ellipsis_mask = 0;
+ if (data.dims() > 0 && std::bernoulli_distribution()(generator())) {
+ int ellipsis_pos =
+ std::uniform_int_distribution<int>(0, data.dims() - 1)(generator());
+ ellipsis_mask = 1LL << ellipsis_pos;
+ }
+
+ int64 new_axis_mask = bitmask_distribution(generator());
+ int64 shrink_axis_mask = bitmask_distribution(generator());
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("StridedSlice")
+ .Input(data)
+ .Input(test::AsTensor<int32>(begin))
+ .Input(test::AsTensor<int32>(end))
+ .Input(test::AsTensor<int32>(strides))
+ .Attr("T", type)
+ .Attr("Index", DT_INT32)
+ .Attr("begin_mask", begin_mask)
+ .Attr("end_mask", end_mask)
+ .Attr("ellipsis_mask", ellipsis_mask)
+ .Attr("new_axis_mask", new_axis_mask)
+ .Attr("shrink_axis_mask", shrink_axis_mask));
+ });
+}
+
+TEST_F(OpTest, StridedSliceGrad) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+
+ // Dimensions of the forward input.
+ std::vector<int64> dims = RandomDims();
+
+ std::vector<int64> begin(dims.size()), end(dims.size());
+ std::vector<int64> strides(dims.size());
+ for (int i = 0; i < dims.size(); ++i) {
+ begin[i] = std::uniform_int_distribution<int64>(-2 * dims[i],
+ 2 * dims[i])(generator());
+ end[i] = std::uniform_int_distribution<int64>(-2 * dims[i],
+ 2 * dims[i])(generator());
+ strides[i] = std::uniform_int_distribution<int64>(
+ -2 * dims[i], 2 * dims[i])(generator());
+ }
+ int64 max_bitmask = (1LL << dims.size()) - 1;
+ std::uniform_int_distribution<int64> bitmask_distribution(0, max_bitmask);
+ int64 begin_mask = bitmask_distribution(generator());
+ int64 end_mask = bitmask_distribution(generator());
+
+ // Create a ellipsis bitmask with at most one 1 bit set.
+ int64 ellipsis_mask = 0;
+ if (!dims.empty() && std::bernoulli_distribution()(generator())) {
+ int ellipsis_pos =
+ std::uniform_int_distribution<int>(0, dims.size() - 1)(generator());
+ ellipsis_mask = 1LL << ellipsis_pos;
+ }
+
+ int64 new_axis_mask = bitmask_distribution(generator());
+ int64 shrink_axis_mask = bitmask_distribution(generator());
+
+ // TODO(phawkins): use shape inference for the forward op to compute the
+ // gradient shape for the backward op. At present, there is a low
+ // probability of the golden op succeeding.
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("StridedSliceGrad")
+ .Input(test::AsTensor<int64>(dims))
+ .Input(test::AsTensor<int64>(begin))
+ .Input(test::AsTensor<int64>(end))
+ .Input(test::AsTensor<int64>(strides))
+ .Input(RandomTensor(type, RandomDims(1)))
+ .Attr("T", type)
+ .Attr("Index", DT_INT64)
+ .Attr("begin_mask", begin_mask)
+ .Attr("end_mask", end_mask)
+ .Attr("ellipsis_mask", ellipsis_mask)
+ .Attr("new_axis_mask", new_axis_mask)
+ .Attr("shrink_axis_mask", shrink_axis_mask));
+ });
+}
+
+TEST_F(OpTest, Tanh) {
+ Repeatedly([this]() {
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Tanh")
+ .Input(RandomTensor(DT_FLOAT))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, TanhGrad) {
+ Repeatedly([this]() {
+ auto dims = RandomDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("TanhGrad")
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Input(RandomTensor(DT_FLOAT, dims))
+ .Attr("T", DT_FLOAT));
+ });
+}
+
+TEST_F(OpTest, Tile) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ Tensor t = RandomTensor(type, RandomDims(1));
+ std::vector<int32> multiples(t.dims());
+ for (int i = 0; i < t.dims(); ++i) {
+ multiples[i] = std::uniform_int_distribution<int>(1, 3)(generator());
+ }
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Tile")
+ .Input(t)
+ .Input(test::AsTensor<int32>(multiples))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, Transpose) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>(kAllXlaTypes);
+ Tensor data = RandomTensor(type);
+ std::vector<int32> perm(data.dims());
+ std::iota(perm.begin(), perm.end(), 0);
+ std::shuffle(perm.begin(), perm.end(), generator());
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("Transpose")
+ .Input(data)
+ .Input(test::AsTensor<int32>(perm))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, TruncateDiv) {
+ Repeatedly([this]() {
+ DataType type = DT_INT32;
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("TruncateDiv")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, TruncateMod) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ auto dims = BroadcastableDims();
+ ExpectTfAndXlaOutputsAreClose(OpTestBuilder("TruncateMod")
+ .Input(RandomTensor(type, dims.first))
+ .Input(RandomTensor(type, dims.second))
+ .Attr("T", type));
+ });
+}
+
+TEST_F(OpTest, ZerosLike) {
+ Repeatedly([this]() {
+ DataType type = Choose<DataType>({DT_INT32, DT_FLOAT});
+ ExpectTfAndXlaOutputsAreClose(
+ OpTestBuilder("ZerosLike").Input(RandomTensor(type)).Attr("T", type));
+ });
+}
+
+} // anonymous namespace
+} // namespace tensorflow
+
+int main(int argc, char** argv) {
+ tensorflow::tf_xla_test_device_ptr = new tensorflow::string("GPU");
+ std::vector<tensorflow::Flag> flag_list = {
+ tensorflow::Flag(
+ "tf_xla_random_seed", &tensorflow::tf_xla_random_seed,
+ "Random seed to use for XLA tests. <= 0 means choose a seed "
+ "nondetermistically."),
+ // TODO(phawkins): it might make more sense to run each test up to a
+ // configurable time bound.
+ tensorflow::Flag("tf_xla_test_repetitions",
+ &tensorflow::tf_xla_test_repetitions,
+ "Number of repetitions for each test."),
+ tensorflow::Flag("tf_xla_test_device", tensorflow::tf_xla_test_device_ptr,
+ "Tensorflow device type to use for test"),
+ tensorflow::Flag("tf_xla_test_use_jit", &tensorflow::tf_xla_test_use_jit,
+ "Use JIT compilation for the operator under test"),
+ };
+ tensorflow::string usage = tensorflow::Flags::Usage(argv[0], flag_list);
+ const bool parse_result = tensorflow::Flags::Parse(&argc, argv, flag_list);
+ if (!parse_result) {
+ LOG(ERROR) << "\n" << usage;
+ return 2;
+ }
+ testing::InitGoogleTest(&argc, argv);
+ if (argc > 1) {
+ LOG(ERROR) << "Unknown argument " << argv[1] << "\n" << usage;
+ return 2;
+ }
+ // XLA devices register kernels at construction time; create and destroy all
+ // known devices to make sure the kernels are registered.
+ std::vector<tensorflow::Device*> devices;
+ TF_CHECK_OK(tensorflow::DeviceFactory::AddDevices(
+ tensorflow::SessionOptions(), "", &devices));
+ for (tensorflow::Device* device : devices) {
+ delete device;
+ }
+ return RUN_ALL_TESTS();
+}