Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make placement of constants follow consumers if they are all on the same device #6615

Merged
merged 12 commits into from Jan 15, 2017
Merged
18 changes: 13 additions & 5 deletions tensorflow/core/common_runtime/simple_placer.cc
Expand Up @@ -605,7 +605,7 @@ bool IsMetadataNode(const Node* node) {
// outputs that are connected to nodes in the same colocation group.
bool IsGeneratorNode(const Node* node) {
return node->num_inputs() == 0 && node->num_outputs() == 1 &&
node->out_edges().size() == 1 && !IsRefType(node->output_type(0));
!IsRefType(node->output_type(0));
}

} // namespace
Expand Down Expand Up @@ -730,9 +730,9 @@ Status SimplePlacer::Run() {
// Heuristic A: prefer to place "generators" with their only
// consumers.
//
// If this is a node with no inputs and a single (non-ref)
// consumer, we save this for a second pass, so that the
// consumer's placement is chosen.
// If this is a node with no inputs and one output, we save
// this for a second pass, so that the consumer's placement
// is chosen.
if (IsGeneratorNode(node)) {
second_pass.push_back(node);
continue;
Expand Down Expand Up @@ -794,7 +794,15 @@ Status SimplePlacer::Run() {
if (IsGeneratorNode(node)) {
const Node* output = (*node->out_edges().begin())->dst();
const string& output_device_name = output->assigned_device_name();
if (CanAssignToDevice(output_device_name, devices)) {

const bool consumers_on_same_device = std::all_of(
node->out_edges().begin(), node->out_edges().end(),
[output_device_name](const Edge* e) {
return e->dst()->assigned_device_name() == output_device_name;
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to replace this entire block of code with:

    bool same_device = std::all_of(node->edges().begin(), node->edges().end(), 
                                   [output_device_name](const Edge& e) { 
                                       return e->dst()->assigned_device_name() == output_device_name;
                                   }) 

If it turns out to be more complicated than this, let me know.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My code pulls the name from the 1st iterator value, then tests it against all of the others. Your code captures 'output_device_name', which is the name from the 1st iterator value. This would need to be read in your version of the code too.

What we need is a:

std::when_calling_with_elements_from_a_collection_every_return_value_of_a_functor_is_the_same()

It might exist. My in-depth knowledge of the std c++ library is limited to pre 2004.

nevertheless - i will replace the while(.....) part of my code with the std::all_of(), since I can still start with the second element.

bool same_device = std::all_of(it, node->out_edges().end(), 
                                   [output_device_name](const Edge* e) { 
                                       return e->dst()->assigned_device_name() == output_device_name;
                                   }) 

if (consumers_on_same_device &&
CanAssignToDevice(output_device_name, devices)) {
assigned_device = output_device_name;
}
}
Expand Down
71 changes: 71 additions & 0 deletions tensorflow/core/common_runtime/simple_placer_test.cc
Expand Up @@ -1226,5 +1226,76 @@ TEST_F(SimplePlacerTest, TestUnsatisfiableConstraintWithReferenceConnections) {
.contains("Cannot colocate nodes 'var' and 'assign'"));
}

// Test that a generator node follows its consumers (where there are several
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Feel free to rename the TestHeuristicA to your name)!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as you suggested, i think we can leave the name alone.

// consumer nodes on the same devices).
TEST_F(SimplePlacerTest, TestGeneratorNodeFollowsConsumerNode) {
Graph g(OpRegistry::Global());
{ // Scope for temporary variables used to construct g.
GraphDefBuilder b(GraphDefBuilder::kFailImmediately);

// A variable is only on CPU
Node* var1_cpu =
ops::SourceOp("VariableCPU", b.opts().WithName("var1_cpu"));
Node* var2_cpu =
ops::SourceOp("VariableCPU", b.opts().WithName("var2_cpu"));

// The constant to be assigned can be on both GPU or CPU.
//
// Because of the heuristic, it gets placed on CPU to avoid a
// copy.
Node* input = ops::SourceOp("TestCPUGPUOutput", b.opts().WithName("in"));

// The assigns are bound to CPU by the reference edge.
ops::BinaryOp("TestAssign", var1_cpu, input, b.opts().WithName("assign1"));
ops::BinaryOp("TestAssign", var2_cpu, input, b.opts().WithName("assign2"));

TF_EXPECT_OK(BuildGraph(b, &g));
}

TF_EXPECT_OK(Place(&g));
EXPECT_COLOCATED(g, "var1_cpu", "in");
EXPECT_COLOCATED(g, "assign1", "in");
EXPECT_COLOCATED(g, "var2_cpu", "in");
EXPECT_COLOCATED(g, "assign2", "in");
}

// Test that a generator node does not follow its consumers (where there are
// several consumers on different devices).
TEST_F(SimplePlacerTest, TestGeneratorNodeDoesntFollowNonColocatedConsumers) {
Graph g(OpRegistry::Global());
{ // Scope for temporary variables used to construct g.
GraphDefBuilder b(GraphDefBuilder::kFailImmediately);

// A variable is only on CPU
Node* var1_cpu =
ops::SourceOp("VariableCPU", b.opts().WithName("var1_cpu"));
Node* var2_cpu =
ops::SourceOp("VariableCPU", b.opts().WithName("var2_cpu"));

// The constant to be assigned can be on both GPU or CPU.
//
// Because of the heuristic, it ought to be on the GPU (cannot be
// co-located with both consumers, so goes to the 'standard' place)
Node* input = ops::SourceOp("TestCPUGPUOutput", b.opts().WithName("in"));

// The assigns are bound to CPU by the reference edge.
ops::BinaryOp("TestAssign", var1_cpu, input, b.opts().WithName("assign1"));
ops::BinaryOp("TestAssign", var2_cpu, input, b.opts().WithName("assign2"));

TF_EXPECT_OK(BuildGraph(b, &g));

GetNodeByName(g, "var1_cpu")
->set_assigned_device_name("/job:a/replica:0/task:0/device:fakecpu:1");

GetNodeByName(g, "var2_cpu")
->set_assigned_device_name("/job:a/replica:0/task:0/device:fakecpu:2");
}

TF_EXPECT_OK(Place(&g));
EXPECT_COLOCATED(g, "assign1", "var1_cpu");
EXPECT_COLOCATED(g, "assign2", "var2_cpu");
EXPECT_DEVICE_TYPE(g, "in", "FakeGPU");
}

} // namespace
} // namespace tensorflow