aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/core/common_runtime/simple_placer_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tensorflow/core/common_runtime/simple_placer_test.cc')
-rw-r--r--tensorflow/core/common_runtime/simple_placer_test.cc863
1 files changed, 863 insertions, 0 deletions
diff --git a/tensorflow/core/common_runtime/simple_placer_test.cc b/tensorflow/core/common_runtime/simple_placer_test.cc
new file mode 100644
index 0000000000..3139962d7e
--- /dev/null
+++ b/tensorflow/core/common_runtime/simple_placer_test.cc
@@ -0,0 +1,863 @@
+#include "tensorflow/core/common_runtime/simple_placer.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "tensorflow/core/common_runtime/device.h"
+#include "tensorflow/core/common_runtime/device_set.h"
+#include "tensorflow/core/framework/device_attributes.pb.h"
+#include "tensorflow/core/framework/graph.pb.h"
+#include "tensorflow/core/framework/kernel_def_builder.h"
+#include "tensorflow/core/framework/op.h"
+#include "tensorflow/core/framework/op_def_builder.h"
+#include "tensorflow/core/framework/op_kernel.h"
+#include "tensorflow/core/graph/graph.h"
+#include "tensorflow/core/graph/graph_def_builder.h"
+#include "tensorflow/core/kernels/ops_util.h"
+#include "tensorflow/core/lib/core/error_codes.pb.h"
+#include "tensorflow/core/lib/core/errors.h"
+#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/lib/strings/strcat.h"
+#include <gtest/gtest.h>
+
+namespace tensorflow {
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Op, kernel, and device registrations to set up the environment.
+//
+// The SimplePlacer uses information about the op (input types),
+// kernel (device constraints), and available devices to make
+// placement decisions. To avoid depending on the full runtime, we
+// define dummy implementations of these, and register them with the
+// runtime.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// A dummy OpKernel that is used to register ops on different devices.
+class DummyOp : public OpKernel {
+ public:
+ explicit DummyOp(OpKernelConstruction* context) : OpKernel(context) {}
+ void Compute(OpKernelContext* context) override {}
+};
+
+// A fake device that has specific device attributes, used to simulate
+// the presence of a CPU or a GPU (without depending on that part of
+// the runtime.
+class FakeDevice : public Device {
+ private:
+ explicit FakeDevice(const DeviceAttributes& device_attributes)
+ : Device(nullptr, device_attributes, nullptr) {}
+
+ public:
+ Status Sync() override { return errors::Unimplemented("FakeDevice::Sync()"); }
+
+ Allocator* GetAllocator(AllocatorAttributes attr) override { return nullptr; }
+
+ static std::unique_ptr<Device> MakeCPU(const string& name) {
+ DeviceAttributes device_attributes;
+ device_attributes.set_name(name);
+ device_attributes.set_device_type(DeviceType(DEVICE_CPU).type());
+ return std::unique_ptr<Device>(new FakeDevice(device_attributes));
+ }
+
+ static std::unique_ptr<Device> MakeGPU(const string& name) {
+ DeviceAttributes device_attributes;
+ device_attributes.set_name(name);
+ device_attributes.set_device_type(DeviceType(DEVICE_GPU).type());
+ return std::unique_ptr<Device>(new FakeDevice(device_attributes));
+ }
+};
+
+// Register the following ops so they can be added to a Graph, and
+// kernels so that they can be placed on particular device types.
+REGISTER_OP("TestVariable").Output("o: Ref(float)");
+REGISTER_KERNEL_BUILDER(Name("TestVariable").Device(DEVICE_CPU), DummyOp);
+REGISTER_KERNEL_BUILDER(Name("TestVariable").Device(DEVICE_GPU), DummyOp);
+
+REGISTER_OP("VariableCPU").Output("o: Ref(float)");
+REGISTER_KERNEL_BUILDER(Name("VariableCPU").Device(DEVICE_CPU), DummyOp);
+
+REGISTER_OP("VariableGPU").Output("o: Ref(float)");
+REGISTER_KERNEL_BUILDER(Name("VariableGPU").Device(DEVICE_GPU), DummyOp);
+
+REGISTER_OP("VariableNoKernels").Output("o: Ref(float)");
+
+REGISTER_OP("TestAdd").Input("a: float").Input("b: float").Output("o: float");
+REGISTER_KERNEL_BUILDER(Name("TestAdd").Device(DEVICE_CPU), DummyOp);
+REGISTER_KERNEL_BUILDER(Name("TestAdd").Device(DEVICE_GPU), DummyOp);
+
+REGISTER_OP("TestRelu").Input("i: float").Output("o: float");
+REGISTER_KERNEL_BUILDER(Name("TestRelu").Device(DEVICE_CPU), DummyOp);
+REGISTER_KERNEL_BUILDER(Name("TestRelu").Device(DEVICE_GPU), DummyOp);
+
+REGISTER_OP("ReluGPU").Input("i: float").Output("o: float");
+REGISTER_KERNEL_BUILDER(Name("ReluGPU").Device(DEVICE_GPU), DummyOp);
+
+REGISTER_OP("TestAssign").Input("i: Ref(float)").Input("v: float");
+REGISTER_KERNEL_BUILDER(Name("TestAssign").Device(DEVICE_CPU), DummyOp);
+REGISTER_KERNEL_BUILDER(Name("TestAssign").Device(DEVICE_GPU), DummyOp);
+
+REGISTER_OP("AssignCPU").Input("i: Ref(float)").Input("v: float");
+REGISTER_KERNEL_BUILDER(Name("AssignCPU").Device(DEVICE_CPU), DummyOp);
+
+REGISTER_OP("AssignGPU").Input("i: Ref(float)").Input("v: float");
+REGISTER_KERNEL_BUILDER(Name("AssignGPU").Device(DEVICE_GPU), DummyOp);
+
+REGISTER_OP("TestInput").Output("a: float").Output("b: float");
+REGISTER_KERNEL_BUILDER(Name("TestInput").Device(DEVICE_CPU), DummyOp);
+
+REGISTER_OP("TestDevice").Output("a: float").Output("b: float");
+REGISTER_KERNEL_BUILDER(Name("TestDevice").Device(DEVICE_GPU), DummyOp);
+
+REGISTER_OP("TestDeviceEnforce").Input("a: Ref(float)").Output("b: float");
+REGISTER_KERNEL_BUILDER(Name("TestDeviceEnforce").Device(DEVICE_CPU), DummyOp);
+REGISTER_KERNEL_BUILDER(Name("TestDeviceEnforce").Device(DEVICE_GPU), DummyOp);
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// A SimplePlacerTest method has three phases:
+//
+// 1. Build a TensorFlow graph, with no (or partial) device assignments.
+// 2. Attempt to compute a placement using the SimplePlacer.
+// 3. EITHER: test that the constraints implied by the graph are respected;
+// or that an appropriate error was reported.
+//
+////////////////////////////////////////////////////////////////////////////////
+class SimplePlacerTest : public ::testing::Test {
+ protected:
+ SimplePlacerTest() {
+ RequireDefaultOps();
+ // Build a set of 10 GPU and 10 CPU devices.
+ // NOTE: this->local_devices_ owns the device objects;
+ // this->devices_ contains borrowed pointers to the device
+ // objects.
+ for (int i = 0; i < 10; ++i) {
+ local_devices_.emplace_back(FakeDevice::MakeCPU(
+ strings::StrCat("/job:a/replica:0/task:0/cpu:", i)));
+ devices_.AddDevice(local_devices_.back().get());
+ // Insert the GPUs in reverse order.
+ local_devices_.emplace_back(FakeDevice::MakeGPU(
+ strings::StrCat("/job:a/replica:0/task:0/gpu:", 9 - i)));
+ devices_.AddDevice(local_devices_.back().get());
+ }
+ }
+
+ // Builds the given graph, and (if successful) indexes the node
+ // names for use in placement, and later lookup.
+ Status BuildGraph(const GraphDefBuilder& builder, Graph* out_graph) {
+ TF_RETURN_IF_ERROR(builder.ToGraph(out_graph));
+ nodes_by_name_.clear();
+ for (Node* node : out_graph->nodes()) {
+ nodes_by_name_[node->name()] = node->id();
+ }
+ return Status::OK();
+ }
+
+ // Invokes the SimplePlacer on "graph". If no DeviceSet is specified, the
+ // placement will use the default DeviceSet (of 10 CPU and 10 GPU devices).
+ //
+ // REQUIRES: "*graph" was produced by the most recent call to BuildGraph.
+ Status Place(Graph* graph, DeviceSet* devices, SessionOptions* options) {
+ SimplePlacer placer(graph, devices, &nodes_by_name_, options);
+ return placer.Run();
+ }
+
+ Status Place(Graph* graph, DeviceSet* devices) {
+ return Place(graph, devices, nullptr);
+ }
+
+ Status Place(Graph* graph, SessionOptions* options) {
+ return Place(graph, &devices_, options);
+ }
+
+ Status Place(Graph* graph) { return Place(graph, &devices_, nullptr); }
+
+ // Returns the node in "graph" with the given name.
+ //
+ // REQUIRES: "graph" was produced by the most recent call to BuildGraph.
+ Node* GetNodeByName(const Graph& graph, const string& name) {
+ const auto search = nodes_by_name_.find(name);
+ CHECK(search != nodes_by_name_.end()) << "Unknown node name: " << name;
+ return graph.FindNodeId(search->second);
+ }
+
+ protected:
+ std::vector<std::unique_ptr<Device>> local_devices_;
+ DeviceSet devices_;
+ SimplePlacer::NodeNameToIdMap nodes_by_name_;
+
+ Status ReferenceTestHelper(const string& variable_op_type,
+ const string& assign_op_type,
+ DeviceType expected_device_type);
+};
+
+#define EXPECT_COLOCATED(g, name_a, name_b) \
+ do { \
+ Graph& g_ = (g); \
+ EXPECT_EQ(GetNodeByName(g_, (name_a))->assigned_device_name(), \
+ GetNodeByName(g_, (name_b))->assigned_device_name()); \
+ } while (0)
+
+#define EXPECT_DEVICE_TYPE(g, name, expected_device_type) \
+ EXPECT_EQ(DeviceType(expected_device_type).type(), \
+ devices_.FindDeviceByName( \
+ GetNodeByName((g), (name))->assigned_device_name()) \
+ ->attributes() \
+ .device_type())
+
+#define EXPECT_DEVICE_CONTAINS(g, name, device_substr) \
+ EXPECT_TRUE(StringPiece(GetNodeByName((g), (name))->assigned_device_name()) \
+ .contains(device_substr))
+
+// Test that a graph with no constraints will successfully assign nodes to the
+// "best available" device (i.e. prefer GPU over CPU).
+TEST_F(SimplePlacerTest, TestNoConstraints) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* input = ops::SourceOp("TestInput", b.opts().WithName("in"));
+ ops::UnaryOp("TestRelu", ops::NodeOut(input, 0), b.opts().WithName("n1"));
+ ops::UnaryOp("TestRelu", ops::NodeOut(input, 1), b.opts().WithName("n2"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ EXPECT_OK(Place(&g));
+ EXPECT_DEVICE_TYPE(g, "in", DEVICE_CPU);
+ EXPECT_DEVICE_TYPE(g, "n1", DEVICE_GPU);
+ EXPECT_DEVICE_TYPE(g, "n2", DEVICE_GPU);
+}
+
+// Test that a graph with device type and reference constraints on
+// some of the ops will successfully assign nodes to the constrained
+// device, and colocate nodes with reference connections.
+TEST_F(SimplePlacerTest, TestDeviceTypeConstraints) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* input = ops::SourceOp("TestInput", b.opts().WithName("in"));
+ Node* var_cpu = ops::SourceOp("VariableCPU", b.opts().WithName("var_cpu"));
+ ops::BinaryOp("AssignCPU", var_cpu, input, b.opts().WithName("assign_cpu"));
+ Node* var_gpu = ops::SourceOp("VariableGPU", b.opts().WithName("var_gpu"));
+ ops::BinaryOp("AssignGPU", var_gpu, input, b.opts().WithName("assign_gpu"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ EXPECT_OK(Place(&g));
+ EXPECT_DEVICE_TYPE(g, "in", DEVICE_CPU);
+ EXPECT_DEVICE_TYPE(g, "var_cpu", DEVICE_CPU);
+ EXPECT_DEVICE_TYPE(g, "assign_cpu", DEVICE_CPU);
+ EXPECT_COLOCATED(g, "var_cpu", "assign_cpu");
+ EXPECT_DEVICE_TYPE(g, "var_gpu", DEVICE_GPU);
+ EXPECT_DEVICE_TYPE(g, "assign_gpu", DEVICE_GPU);
+ EXPECT_COLOCATED(g, "var_gpu", "assign_gpu");
+}
+
+// Test that a graph with partial device specifications on the ops
+// will successfully
+TEST_F(SimplePlacerTest, TestPartialSpec) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in").WithDevice("/job:a"));
+ ops::SourceOp("TestVariable",
+ b.opts().WithName("var").WithDevice("/job:a"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ EXPECT_OK(Place(&g));
+ EXPECT_DEVICE_TYPE(g, "in", DEVICE_CPU);
+ EXPECT_DEVICE_CONTAINS(g, "in", "/job:a");
+ EXPECT_DEVICE_TYPE(g, "var", DEVICE_GPU);
+ EXPECT_DEVICE_CONTAINS(g, "var", "/job:a");
+}
+
+// Test that a node with an assigned device is not relocated.
+TEST_F(SimplePlacerTest, TestAssignedDevicePreserved) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ GetNodeByName(g, "in")
+ ->set_assigned_device_name("/job:a/replica:0/task:0/cpu:7");
+
+ EXPECT_OK(Place(&g));
+ EXPECT_EQ("/job:a/replica:0/task:0/cpu:7",
+ GetNodeByName(g, "in")->assigned_device_name());
+}
+
+// Test that a graph with partial device specifications for CPU-only ops
+// will be relocated to CPU.
+TEST_F(SimplePlacerTest, TestPartialSpecGpuToCpu) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in").WithDevice("/gpu:0"));
+ ops::SourceOp("TestVariable",
+ b.opts().WithName("var").WithDevice("/gpu:0"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ SessionOptions options;
+ options.config.set_allow_soft_placement(true);
+ EXPECT_OK(Place(&g, &options));
+ EXPECT_DEVICE_TYPE(g, "in", DEVICE_CPU);
+ EXPECT_DEVICE_CONTAINS(g, "in", "/cpu");
+ EXPECT_DEVICE_TYPE(g, "var", DEVICE_GPU);
+ EXPECT_DEVICE_CONTAINS(g, "var", "/gpu:0");
+}
+
+// Test that a node with an assigned GPU device but has not registered
+// OpKernel will fail.
+TEST_F(SimplePlacerTest, TestAssignedGpuDeviceToCpuDevice) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ GetNodeByName(g, "in")
+ ->set_assigned_device_name("/job:a/replica:0/task:0/gpu:0");
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INTERNAL, s.code());
+ EXPECT_TRUE(
+ StringPiece(s.error_message())
+ .contains("Assigned device '/job:a/replica:0/task:0/gpu:0' "
+ "does not have registered OpKernel support for TestInput"));
+}
+
+// Test that graphs with reference connections are correctly placed.
+
+// Build a graph containing a Variable op of "variable_op_type" and an
+// Assign op of "assign_op_type", and expect all of the ops to be
+// placed on a device of type "expected_device_type".
+Status SimplePlacerTest::ReferenceTestHelper(const string& variable_op_type,
+ const string& assign_op_type,
+ DeviceType expected_device_type) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* input = ops::SourceOp("TestInput", b.opts().WithName("in"));
+ // Build ten variable-and-assignment pairs.
+ for (int i = 0; i < 10; ++i) {
+ Node* var = ops::SourceOp(variable_op_type,
+ b.opts().WithName(strings::StrCat("var_", i)));
+ ops::BinaryOp(assign_op_type, var, input,
+ b.opts().WithName(strings::StrCat("assign_", i)));
+ }
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ TF_RETURN_IF_ERROR(Place(&g));
+
+ for (int i = 0; i < 10; ++i) {
+ EXPECT_COLOCATED(g, strings::StrCat("var_", i),
+ strings::StrCat("assign_", i));
+ EXPECT_DEVICE_TYPE(g, strings::StrCat("var_", i), expected_device_type);
+ EXPECT_DEVICE_TYPE(g, strings::StrCat("assign_", i), expected_device_type);
+ }
+
+ return Status::OK();
+}
+
+// Test all 2^3 combinations of Variable and Assignment op types
+// (unconstrained, CPU-only, and GPU-only).
+TEST_F(SimplePlacerTest, TestReferenceConnection) {
+ Status s;
+ EXPECT_OK(ReferenceTestHelper("TestVariable", "TestAssign", DEVICE_GPU));
+ EXPECT_OK(ReferenceTestHelper("TestVariable", "AssignCPU", DEVICE_CPU));
+ EXPECT_OK(ReferenceTestHelper("TestVariable", "AssignGPU", DEVICE_GPU));
+ EXPECT_OK(ReferenceTestHelper("VariableCPU", "TestAssign", DEVICE_CPU));
+ EXPECT_OK(ReferenceTestHelper("VariableCPU", "AssignCPU", DEVICE_CPU));
+ {
+ Status s = ReferenceTestHelper("VariableCPU", "AssignGPU", DEVICE_CPU);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("no device type supports both of those nodes"));
+ }
+ EXPECT_OK(ReferenceTestHelper("VariableGPU", "TestAssign", DEVICE_GPU));
+ {
+ Status s = ReferenceTestHelper("VariableGPU", "AssignCPU", DEVICE_CPU);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("no device type supports both of those nodes"));
+ }
+ EXPECT_OK(ReferenceTestHelper("VariableGPU", "AssignGPU", DEVICE_GPU));
+}
+
+// Test the handling of '@node_name' colocation constraints, when
+// these are arranged in multiple chains.
+TEST_F(SimplePlacerTest, TestColocatedChain) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* input = ops::SourceOp("TestInput", b.opts().WithName("in"));
+ Node* last_node = input;
+ for (int i = 0; i < 100; ++i) {
+ if (i % 10 == 0) {
+ // Every ten nodes, start a new chain.
+ last_node = ops::UnaryOp("TestRelu", last_node,
+ b.opts().WithName(strings::StrCat("n_", i)));
+ } else {
+ // Chain each successive node to the previous one.
+ last_node =
+ ops::UnaryOp("TestRelu", last_node,
+ b.opts()
+ .WithName(strings::StrCat("n_", i))
+ .WithDevice(strings::StrCat("@n_", i - 1)));
+ }
+ }
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ EXPECT_OK(Place(&g));
+ for (int i = 0; i < 100; ++i) {
+ if (i % 10 != 0) {
+ EXPECT_COLOCATED(g, strings::StrCat("n_", i - (i % 1)),
+ strings::StrCat("n_", i));
+ }
+ }
+}
+
+// Test the handling of '@node_name' colocation constraints, when the
+// chains are shuffled.
+TEST_F(SimplePlacerTest, TestColocatedChainWithLongRangeColocations) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* input = ops::SourceOp("TestInput", b.opts().WithName("in"));
+ Node* last_node = input;
+ for (int i = 0; i < 10; ++i) {
+ // Start ten chains.
+ last_node = ops::UnaryOp("TestRelu", last_node,
+ b.opts().WithName(strings::StrCat("n_", i)));
+ }
+ for (int i = 10; i < 100; ++i) {
+ // Add each node to the (i % 10)^th chain.
+ last_node = ops::UnaryOp("TestRelu", last_node,
+ b.opts()
+ .WithName(strings::StrCat("n_", i))
+ .WithDevice(strings::StrCat("@n_", i % 10)));
+ }
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ EXPECT_OK(Place(&g));
+ for (int i = 10; i < 100; ++i) {
+ EXPECT_COLOCATED(g, strings::StrCat("n_", i % 10),
+ strings::StrCat("n_", i));
+ }
+}
+
+TEST_F(SimplePlacerTest, TestColocationAndReferenceConnections) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* input = ops::SourceOp("TestInput", b.opts().WithName("in"));
+ for (int i = 0; i < 10; ++i) {
+ // Declare ten variable and assignment pairs.
+ Node* var = ops::SourceOp("TestVariable",
+ b.opts().WithName(strings::StrCat("var_", i)));
+ ops::BinaryOp("TestAssign", var, input,
+ b.opts().WithName(strings::StrCat("assign_", i)));
+ }
+ for (int i = 10; i < 100; ++i) {
+ // Create a variable colocated with some existing variable, and
+ // an assignment colocated with a possibly-different variable.
+ Node* var = ops::SourceOp(
+ "TestVariable", b.opts()
+ .WithName(strings::StrCat("var_", i))
+ .WithDevice(strings::StrCat("@var_", i % 6)));
+ ops::BinaryOp("TestAssign", var, input,
+ b.opts()
+ .WithName(strings::StrCat("assign_", i))
+ .WithDevice(strings::StrCat("@assign_", i % 3)));
+ }
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ EXPECT_OK(Place(&g));
+ for (int i = 0; i < 10; ++i) {
+ EXPECT_COLOCATED(g, strings::StrCat("var_", i),
+ strings::StrCat("assign_", i));
+ }
+ for (int i = 10; i < 100; ++i) {
+ EXPECT_COLOCATED(g, strings::StrCat("var_", i),
+ strings::StrCat("assign_", i));
+ EXPECT_COLOCATED(g, strings::StrCat("var_", i),
+ strings::StrCat("var_", i % 6));
+ EXPECT_COLOCATED(g, strings::StrCat("assign_", i),
+ strings::StrCat("assign_", i % 3));
+ }
+}
+
+// Test that placement fails when no devices are registered.
+TEST_F(SimplePlacerTest, TestEmptyDeviceSet) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ DeviceSet empty;
+
+ Status s = Place(&g, &empty);
+ EXPECT_TRUE(
+ StringPiece(s.error_message()).contains("No devices are registered"));
+}
+
+// Test that placement fails when the requested device forces an
+// indirect constraint to be violated.
+TEST_F(SimplePlacerTest, TestHeterogeneousDeviceSetFailure) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* in = ops::SourceOp("TestInput", b.opts().WithName("in"));
+ Node* var = ops::SourceOp("VariableGPU", b.opts().WithName("var"));
+ ops::BinaryOp("TestAssign", var, in,
+ b.opts().WithName("assign").WithDevice("/job:b/task:1"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ DeviceSet heterogeneous;
+ std::unique_ptr<Device> gpu(
+ FakeDevice::MakeGPU("/job:b/replica:0/task:0/gpu:0"));
+ heterogeneous.AddDevice(gpu.get());
+ std::unique_ptr<Device> cpu(
+ FakeDevice::MakeCPU("/job:b/replica:0/task:1/cpu:0"));
+ heterogeneous.AddDevice(cpu.get());
+ Status s = Place(&g, &heterogeneous);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("colocated with a group of nodes that required "
+ "incompatible device"));
+}
+
+// Test that placement fails when an unknown device is requested.
+TEST_F(SimplePlacerTest, TestUnknownDevice) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in").WithDevice("/job:foo"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(
+ StringPiece(s.error_message())
+ .contains(
+ "Could not satisfy explicit device specification '/job:foo'"));
+}
+
+// Test that placement fails when the combination of partial
+// constraints leads to an unknown device.
+TEST_F(SimplePlacerTest, TestUnknownMergedDevice) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in").WithDevice("/job:foo"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(
+ StringPiece(s.error_message())
+ .contains(
+ "Could not satisfy explicit device specification '/job:foo'"));
+}
+
+// Test that placement fails when the previously-assigned device for a
+// node is unknown.
+TEST_F(SimplePlacerTest, TestUnknownAssignedDevice) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ GetNodeByName(g, "in")->set_assigned_device_name("/job:foo");
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INTERNAL, s.code());
+ EXPECT_TRUE(
+ StringPiece(s.error_message())
+ .contains("Assigned device '/job:foo' does not match any device"));
+}
+
+// Test that placement fails when an op with no registered kernels is
+// requested.
+TEST_F(SimplePlacerTest, TestNoKernelsRegistered) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("VariableNoKernels", b.opts().WithName("var"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(
+ StringPiece(s.error_message())
+ .contains(
+ "No OpKernel was registered to support Op 'VariableNoKernels'"));
+}
+
+// Test that placement fails when a kernel is registered but no known
+// device supports it.
+TEST_F(SimplePlacerTest, TestNoDevicesRegistered) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("VariableGPU", b.opts().WithName("var"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ DeviceSet cpu_only;
+ std::unique_ptr<Device> cpu(
+ FakeDevice::MakeCPU("/job:a/replica:0/task:0/cpu:0"));
+ cpu_only.AddDevice(cpu.get());
+
+ Status s = Place(&g, &cpu_only);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("No OpKernel was registered to support "
+ "Op 'VariableGPU'"));
+}
+
+// Test that placement fails when a requested device is malformed.
+TEST_F(SimplePlacerTest, TestMalformedDeviceSpecification) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in").WithDevice("/foo:bar"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("Malformed device specification '/foo:bar'"));
+}
+
+// Test that placement fails when a previously-assigned device is malformed.
+TEST_F(SimplePlacerTest, TestMalformedAssignedDevice) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ GetNodeByName(g, "in")->set_assigned_device_name("/foo:bar");
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INTERNAL, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("Malformed assigned device '/foo:bar'"));
+}
+
+// Test that placement fails when a device was previously assigned to
+// a node, but it does not uniquely identify a particular device.
+TEST_F(SimplePlacerTest, TestNonUniqueAssignedDevice) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ GetNodeByName(g, "in")->set_assigned_device_name("/job:a");
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INTERNAL, s.code());
+ EXPECT_TRUE(
+ StringPiece(s.error_message())
+ .contains("Assigned device '/job:a' does not match any device"));
+}
+
+// Test that placement fails when a node requests colocation with another
+// node that does not exist.
+TEST_F(SimplePlacerTest, TestUnknownColocatedNode) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in").WithDevice("@foo"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message()).contains("'foo' does not exist"));
+}
+
+// Test that placement fails when a node requests colocation with a
+// malformed node name.
+TEST_F(SimplePlacerTest, TestMalformedColocatedNode) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestInput", b.opts().WithName("in").WithDevice("@"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("node named in device '' does not exist"));
+}
+
+// Test that ops request to be placed on non-existent devices will be relocated
+// to existing device of the same type if allow_soft_placement is set.
+TEST_F(SimplePlacerTest, TestNonexistentGpuAllowSoftPlacement) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestDevice", b.opts().WithName("in").WithDevice("/gpu:11"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ SessionOptions options;
+ options.config.set_allow_soft_placement(true);
+ EXPECT_OK(Place(&g, &options));
+ EXPECT_DEVICE_CONTAINS(g, "in", "/gpu:0");
+}
+
+// Test that ops request to be placed on non-existent devices will fail if
+// allow_soft_placement is not set.
+TEST_F(SimplePlacerTest, TestNonexistentGpuNoAllowSoftPlacement) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("TestDevice", b.opts().WithName("in").WithDevice("/gpu:11"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ SessionOptions options;
+ Status s = Place(&g, &options);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(
+ StringPiece(s.error_message())
+ .contains(
+ "Could not satisfy explicit device specification '/gpu:11'"));
+}
+
+// Test that placement fails when a node requests an explicit device that is not
+// supported by the registered kernels if allow_soft_placement is no set.
+TEST_F(SimplePlacerTest, TestUnsupportedDeviceNoAllowSoftPlacement) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("VariableGPU", b.opts().WithName("var").WithDevice("/cpu:0"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ SessionOptions options;
+ Status s = Place(&g, &options);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(
+ StringPiece(s.error_message())
+ .contains(
+ "Could not satisfy explicit device specification '/cpu:0'"));
+}
+
+TEST_F(SimplePlacerTest, TestUnsupportedDeviceAllowSoftPlacement) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ ops::SourceOp("VariableGPU", b.opts().WithName("var").WithDevice("/cpu:0"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ SessionOptions options;
+ options.config.set_allow_soft_placement(true);
+ EXPECT_OK(Place(&g, &options));
+}
+
+// Test that a graph with device type and reference constraints on
+// some of the ops will successfully assign nodes to the constrained
+// device, and colocate nodes with reference connections.
+TEST_F(SimplePlacerTest, TestDeviceTypeConstraintsAllowSoftPlacement) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ // var_gpu has ref output and runs on GPU.
+ // force_gpu takes var_gpu and requested CPU.
+ // Verify that both are placed on GPU.
+ Node* var_gpu = ops::SourceOp("VariableGPU", b.opts().WithName("var_gpu"));
+ ops::UnaryOp("TestDeviceEnforce", var_gpu,
+ b.opts().WithName("force_gpu").WithDevice("/cpu:0"));
+ // var_cpu has ref output and runs on CPU.
+ // force_cpu takes var_cpu and requested GPU.
+ // Verify that both are placed on CPU.
+ Node* var_cpu = ops::SourceOp("VariableCPU", b.opts().WithName("var_cpu"));
+ ops::UnaryOp("TestDeviceEnforce", var_cpu,
+ b.opts().WithName("force_cpu").WithDevice("/gpu:0"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ SessionOptions options;
+ options.config.set_allow_soft_placement(true);
+ EXPECT_OK(Place(&g, &options));
+ EXPECT_DEVICE_TYPE(g, "var_gpu", DEVICE_GPU);
+ EXPECT_DEVICE_TYPE(g, "force_gpu", DEVICE_GPU);
+ EXPECT_COLOCATED(g, "var_gpu", "force_gpu");
+ EXPECT_DEVICE_TYPE(g, "var_cpu", DEVICE_CPU);
+ EXPECT_DEVICE_TYPE(g, "force_cpu", DEVICE_CPU);
+ EXPECT_COLOCATED(g, "var_cpu", "force_cpu");
+}
+
+// Test that placement fails when two nodes have a reference connection
+// constraint, and each node requires a mutually incompatible device.
+TEST_F(SimplePlacerTest, TestUnsatisfiableConstraintWithReferenceConnections) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* var = ops::SourceOp("VariableGPU", b.opts().WithName("var"));
+ Node* input = ops::SourceOp("TestInput", b.opts().WithName("in"));
+ ops::BinaryOp("AssignCPU", var, input, b.opts().WithName("assign"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("Cannot colocate nodes 'var' and 'assign'"));
+}
+
+// Test that placement fails when two nodes have an explicit
+// colocation constraint, and each node requires a mutually
+// incompatible device.
+TEST_F(SimplePlacerTest, TestUnsatisfiableConstraintWithColocatedNodes) {
+ Graph g(OpRegistry::Global());
+ { // Scope for temporary variables used to construct g.
+ GraphDefBuilder b(GraphDefBuilder::kFailImmediately);
+ Node* input = ops::SourceOp("TestInput",
+ b.opts().WithName("in").WithDevice("/gpu:0"));
+ Node* relu_1 = ops::UnaryOp("TestRelu", input,
+ b.opts().WithName("relu_1").WithDevice("@in"));
+ ops::UnaryOp("ReluGPU", relu_1,
+ b.opts().WithName("relu_2").WithDevice("@relu_1"));
+ EXPECT_OK(BuildGraph(b, &g));
+ }
+
+ Status s = Place(&g);
+ EXPECT_EQ(error::INVALID_ARGUMENT, s.code());
+ EXPECT_TRUE(StringPiece(s.error_message())
+ .contains("Cannot colocate nodes 'relu_1' and 'relu_2'"));
+}
+
+} // namespace
+} // namespace tensorflow