Skip to content

[Graph Partitioning] Add optimization to minimize communication cost and number of partitions. #2359

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

Merged
merged 1 commit into from
Feb 11, 2019

Conversation

beicy
Copy link
Contributor

@beicy beicy commented Feb 6, 2019

Description:
Based on the initial partitions, this PR adds:

  1. Adjust the initial partition (moving nodes from one partition to another partition) to minimize the communication cost.
  2. Combine the partitions if necessary.

Testing:
For unittest PartitionerTest::Basic1, the communication cost is 288 bytes->256 bytes.
For PartitionerTest::Basic2, the communication cost is 160 bytes->64 bytes. And the number of partitions is 3 -> 2. The following 2 pics shows the initial partition and optimized partitions:
The initial partition ( Memory 2048 bytes. 3 partitions, with memory (1152 bytes, 1152 bytes, 1376 bytes)
f1

The optimized partition (Memory 2048 bytes. 2 partitions, with memory (1728, 1728) :
new2

Documentation: #2298
[Optional Fixes #issue]

Please see a detailed explanation of how to fill out the fields in the relevant sections in PULL_REQUEST.md.

@@ -30,19 +31,30 @@ namespace glow {
using namespace runtime;

using MemUsageMap = std::unordered_map<Node *, unsigned>;
using NodesSet = std::set<Node *>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor nit: But I think we tend to call most type aliases something like TypeXyzTy, i.e. Ty at the end.

@@ -102,6 +124,19 @@ class Partitioner {
/// function.
void initOpMemUsage();

/// Combine the partitions if necessary : if all outside uses of the nodes in
/// /// partition1 is in partition2, and the sum of memory comsuption of
Copy link
Contributor

Choose a reason for hiding this comment

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

Two /// are used here.

Copy link
Contributor

Choose a reason for hiding this comment

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

typo: comsuption -> consumption

namespace glow {

/// Given \p nodes, return a list of nodes who use any node in this set.
std::vector<Node *> getOutUsers(std::set<Node *> nodes) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you really need to pass nodes by value? Why not const std::set<Node *> &?


/// Given \p nodes, return a list of nodes who use only the nodes in this set or
/// constant.
std::vector<Node *> getOutUsersWithOnePre(std::set<Node *> nodes) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you really need to pass nodes by value? Why not const std::set<Node *> &?

Copy link
Contributor

Choose a reason for hiding this comment

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

In getOutUsersWithOnePre what does the Pre part means? Predecessor? May be it should be a bit longer than Pre to be more descriptive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it means "Predecessor" :)

Node *cur = *it;
// For Save onde, the only required memory is for output.
if (cur->getKind() == Kinded::Kind::SaveNodeKind) {
SaveNode *tmp = (SaveNode *)cur;
Copy link
Contributor

Choose a reason for hiding this comment

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

Use llvm::cast instead of C-style cast.

Copy link
Contributor

Choose a reason for hiding this comment

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

Or even better:

if (auto *SN = llvm::dyn_cast<SaveNode>(cur)) {...}

continue;
}
Function *cur = (*it).first;
uint64_t mem =
Copy link
Contributor

Choose a reason for hiding this comment

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

May be call it memCost?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or memSize

if (memUsage_[outUsers[i]] + mem > availableMemory) {
continue;
}
// Rule 2: this move won't cause constant dupication.
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: May be it is OK to have constants duplication if the size of a constant is below a certain threshold?

Copy link
Contributor

Choose a reason for hiding this comment

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

typo: dupication -> duplication

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Question: May be it is OK to have constants duplication if the size of a constant is below a certain threshold?

@opti-mix Theoretically, it should be allowed if it reduce the communication cost a lot or good for workload balance. Will think about it for the next step. Thanks!

if (cont) {
continue;
}
// Rule 3: this move won't increase communication cost. Even this move
Copy link
Contributor

Choose a reason for hiding this comment

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

s/Even this/Even if this/

// or paritionCombine.
nSet.insert(outUsers[i]);
GraphMemInfo cost = getGraphMemInfo(nSet);
if (cost.outMem <= communicationCost) {
Copy link
Contributor

Choose a reason for hiding this comment

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

again, may be outMemSize or outMemCost instead of outMem?

if (nodesSet[suc].empty()) {
// It is possible that after moving a node from Partition2 to
// Partition1, Partition2 become empty. Remove the empty partition.
partitions.deletePartition(suc);
Copy link
Contributor

Choose a reason for hiding this comment

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

Here you could reuse the partition removal logic if it would be a separate method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

here nodesSet is not partitions's member, and this is just one circumstance under which a partition need to be removed. Therefore, I only add deletePartition as a method.

partitions.setCost((*it).first, cost);
}

// Move/Exchange nodes between any two connected partions, until no gain is
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: partions -> partitions

@@ -354,12 +495,14 @@ DAGNodeList &Partitioner::Partition() {

doPartitioning(F_, partitionMap);

F_->dumpDAG("f1.dot");
Copy link
Contributor

Choose a reason for hiding this comment

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

stale debug code

// Remove the original function after partitioning.
module_->eraseFunction(F_);

auto funcList = module_->getFunctions();
for (Function *F : funcList) {
(void)F;
F->dumpDAG("f2.dot");
Copy link
Contributor

Choose a reason for hiding this comment

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

stale debug code

@@ -1,5 +1,6 @@
add_library(Partitioner
Partitioner.cpp)
PartitionUtils.cpp
Copy link
Contributor

Choose a reason for hiding this comment

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

major nit, but: can we rename PartitionUtils.{cpp, h} to PartitionerUtils.{cpp, h}? Just for consistency in other file names, since we have Partitioner.{cpp, h}, PartitionerTest.cpp, etc.

/// The memory usage of a subgraph (i.e. a list of nodes of a function).
struct GraphMemInfo {
// The memory usage of all input nodes (whose predecessors are not included in
// this subgraph) of this subgraph
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add period at end of comment.


#include "glow/Partitioner/PartitionUtils.h"

#include <iostream>
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need this?

if (memUsage_[outUsers[i]] + mem > availableMemory) {
continue;
}
// Rule 2: this move won't cause constant dupication.
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: dupication -> duplication

// Step1 Move: Assume Partition1 -> Partition2, try to move nodes from
// Partition2 to Partition1 if those nodes only use the nodes in
// Partition1(recursively) and the move won't make Partition1's memory exceeds
// the memory constratint, and the communication cost is minimized.
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: constratint -> constraint

}
// Rule 3: this move won't increase communication cost. Even this move
// won't change communication cost, according to rule 1 and rule 2, the
// memory comsuption of the partition where this node (i.e outUsers[i])
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: comsuption -> consumption

@@ -139,6 +139,147 @@ static BFSLevel getBFSLevel(Function *F) {
return bfs;
}

// Combine the partitions if necessary : if all outside uses of the nodes in
// partition1 is in partition2, and the sum of memory comsuption of partition1
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: comsuption -> consumption

Copy link
Contributor

@gcatron gcatron left a comment

Choose a reason for hiding this comment

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

This looks great! Approved pending addressing comments from @opti-mix and @jfix71.


namespace glow {

/// Given \p nodes, return a list of nodes who use any node in this set.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is really any nodes who use this set that are not in this set right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes :)

@beicy beicy merged commit 517f4de into pytorch:master Feb 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants