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
[Partitioner] Heterogeneous partition testing #3197
Conversation
@beicy Nice! Could you also add an option to specify the opposite? The following should mean that only AvgPool is supported on the specific backend and everything else should be considered not supported by the backend.
I hope adding this options should be trivial. |
/// supported by this DeviceManger. It doesn't mean that those nodes kind are | ||
/// not supported by this device. It just means user don't want these kind of | ||
/// nodes running on this device. | ||
llvm::StringRef getNonSupprtedNodes() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is probably better to implement getParamByName(llvm::StringRef name)
to make it more reusable. And then you can just call it like getParamByName("nonSupportedOp")
lib/Partitioner/PartitionerUtils.cpp
Outdated
std::set<Kinded::Kind> generateNonSupportedNodes(llvm::StringRef names) { | ||
std::set<Kinded::Kind> ret; | ||
while (names.find(',') != std::string::npos) { | ||
std::pair<llvm::StringRef, llvm::StringRef> newSplit = names.split(','); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not very important, but could it be that split allocates new strings? Please have a look at the similar function for splitting comma-separated input here:
https://llvm.org/doxygen/CommandLine_8cpp_source.html#l00607
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/me wishes for folly::split
(but of course we don't want to pull in folly, alas)
5920f3d
to
a5eacc1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding supportedNodes
!
Please address a couple of minor comments below and the PR should be good to merge!
lib/Partitioner/Partitioner.cpp
Outdated
// Step 3: Check if the node is actually supported in this backend, if so, | ||
// assign it to this backend and now we find the first backend that | ||
// supports this node (The order of backends is important) and break. | ||
// Otherwise goto Step 4. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Step 3 sounds odd. I mean this part:
if so, assign it to this backend and now we find the first backend that supports this node
. Could you rephrase?
lib/Partitioner/Partitioner.cpp
Outdated
// Step 1: | ||
auto nonSupported = | ||
backendMap_[backend->getBackendName()].nonSupportedNodesKind; | ||
if (nonSupported.find(N.getKind()) != nonSupported.end()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nit: The following could be a bit shorter:
if (nonSupported.count(N.getKind()) {
continue;
}
@@ -38,6 +38,10 @@ struct BackendInfo { | |||
uint64_t memSize; | |||
/// Backend pointer. | |||
Backend *backend = nullptr; | |||
/// The non-supported nodes kind. | |||
std::set<Kinded::Kind> nonSupportedNodesKind; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You store multiple kinds in the set, right? So, should it be called nonSupportedNodesKinds
? Same for supportedNodesKinds
?
lib/Partitioner/Partitioner.cpp
Outdated
// assigned to the next backend. | ||
|
||
// Step 1: | ||
auto nonSupported = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto nonSupportedNodesKinds
?
lib/Partitioner/Partitioner.cpp
Outdated
continue; | ||
} | ||
// Step 2: | ||
auto supported = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
supportedNodesKinds
?
lib/Partitioner/Partitioner.cpp
Outdated
// Step 2: | ||
auto supported = | ||
backendMap_[backend->getBackendName()].supportedNodesKind; | ||
if (supported.size() > 0 && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about a simpler form?
if (!supprted.empty() && supported.count(N.getKind())) {
// Continue.
}
lib/Partitioner/PartitionerUtils.cpp
Outdated
@@ -286,4 +286,16 @@ GraphMemInfo getGraphMemInfo(const NodesSetTy &nodes) { | |||
return ret; | |||
} | |||
|
|||
std::set<Kinded::Kind> generateNodeKindsSet(llvm::StringRef names) { | |||
std::set<Kinded::Kind> ret; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ret -> nodeKindsSet?
- name: Device1 | ||
backendName: CPU | ||
parameters: | | ||
"nonSupportedNodes":"AvgPool" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add supportedNodes
example as well, because it needs to be tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "Device3" in this file has the "supportedNodes" params. Since there are many ops in resnet50, I just let Interpreter supports all. In unit test, we tested the supported nodes set.
@@ -38,6 +38,10 @@ struct BackendInfo { | |||
uint64_t memSize; | |||
/// Backend pointer. | |||
Backend *backend = nullptr; | |||
/// The non-supported nodes kind. | |||
std::set<Kinded::Kind> nonSupportedNodesKind; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless order matters here, prefer llvm::DenseSet
or maybe std::unordered_set
.
include/glow/Runtime/RuntimeTypes.h
Outdated
@@ -51,6 +51,14 @@ struct DeviceInfo { | |||
uint64_t availableMemory; | |||
/// Backend Type. | |||
std::string backendName; | |||
/// A string contains the node names(e.g. Add, Div) which are seperated by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sp: separated
// is important. | ||
// is important. The check flow is : | ||
|
||
// Step 1: If a node is in pre-defined non-supported nodes set, it can not |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's just my preference but here I would find it easier to read the "step N" comments inline with the code, rather than as a separate block that I need to scroll up to reference. And that way we could get rid of the "step N" tags themselves, which have to be kept up to date over time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
lib/Partitioner/Partitioner.cpp
Outdated
// assigned to the next backend. | ||
|
||
// Step 1: | ||
auto nonSupported = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably want const auto &nonSupported = ...
here, or this is going to invoke copy assignment.
lib/Partitioner/Partitioner.cpp
Outdated
auto nonSupported = | ||
backendMap_[backend->getBackendName()].nonSupportedNodesKind; | ||
if (nonSupported.find(N.getKind()) != nonSupported.end()) { | ||
// This op is on the pre-definded non-supported op list: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sp: pre-defined
lib/Partitioner/PartitionerUtils.cpp
Outdated
std::set<Kinded::Kind> generateNonSupportedNodes(llvm::StringRef names) { | ||
std::set<Kinded::Kind> ret; | ||
while (names.find(',') != std::string::npos) { | ||
std::pair<llvm::StringRef, llvm::StringRef> newSplit = names.split(','); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/me wishes for folly::split
(but of course we don't want to pull in folly, alas)
efab6ca
to
ba8aa97
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation part is fine now! Thanks!
All of my comments this time are about tests.
// heterogeneous partiton. | ||
ASSERT_EQ(node->logicalDevices.size(), 1); | ||
} | ||
createSimpleModule(mod_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what exactly you test here. You set up the backends, but you do not expect anything specific from partitions. Or do you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those backends are mock backends which are subclass of "Interpreter" or "CPU". They overload the isOpsupported() function to eliminate some nodes for Heterogeneous testing. Previously, we didn't have such "nonSupportedNodesKinds" nor "supportedNodesKinds" config. Therefore, we need to write mock backends for unittest test.
tests/unittests/PartitionerTest.cpp
Outdated
CompilationContext cctx; | ||
auto err = partitioner.Partition(cctx); | ||
EXPECT_FALSE(errToBool(std::move(err))); | ||
DAGListTy myList = std::move(partitioner.getPartitionResult()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you give this variable a more meaningful name than myList
? ;-)
tests/unittests/PartitionerTest.cpp
Outdated
CompilationContext cctx; | ||
auto err = partitioner.Partition(cctx); | ||
EXPECT_FALSE(errToBool(std::move(err))); | ||
DAGListTy myList = std::move(partitioner.getPartitionResult()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you give this variable a more meaningful name than myList? ;-)
tests/unittests/PartitionerTest.cpp
Outdated
} | ||
|
||
/// Test pre-defined non-supported ops used for choosing backend in | ||
/// Heterogeneous Partition. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not obvious to me from the code and comments below how the non-supported ops are tested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The config yaml file is only loaded in "loader" flow (I added resnet50 test in tests/images/run.sh to test loading and parsing the yaml file). For Partitioner unnitest, we just pass the pre-defined nodes list to DeviceInfo used in Partitioner. Added comments in the tests.
tests/unittests/PartitionerTest.cpp
Outdated
|
||
/// Test pre-defined non-supported ops used for choosing backend in | ||
/// Heterogeneous Partition. | ||
TEST_F(PartitionerTest, ConfigHeterogeneousPartitioning1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you give this test a more meaningful name reflecting what it does?
tests/unittests/PartitionerTest.cpp
Outdated
|
||
/// Test pre-defined supported ops used for choosing backend in | ||
/// Heterogeneous Partition. | ||
TEST_F(PartitionerTest, ConfigHeterogeneousPartitioning2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you give this test a more meaningful name reflecting what it does?
tests/unittests/PartitionerTest.cpp
Outdated
} | ||
|
||
/// Test pre-defined supported ops used for choosing backend in | ||
/// Heterogeneous Partition. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not obvious to me from the code and comments below how the supported ops are tested.
tests/unittests/PartitionerTest.cpp
Outdated
CompilationContext cctx; | ||
auto err = partitioner.Partition(cctx); | ||
EXPECT_FALSE(errToBool(std::move(err))); | ||
DAGListTy myList = std::move(partitioner.getPartitionResult()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you give this variable a more meaningful name than myList? ;-)
06a9659
to
d7f0189
Compare
@@ -981,6 +1003,10 @@ void Partitioner::getBackendMap( | |||
// is the same. | |||
// TODO : will improve the algorithm for different memory size. | |||
backendInfo.memSize = deviceInfo_[i].availableMemory; | |||
backendInfo.nonSupportedNodesKinds = | |||
generateNodeKindsSet(deviceInfo_[i].nonSupportedNodes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe std::move(generateNodeKindsSet(deviceInfo_[i].nonSupportedNodes))
to avoid copying the set?
tests/unittests/PartitionerTest.cpp
Outdated
ASSERT_EQ(dagList.size(), 1); | ||
ASSERT_TRUE(checkSaveNode(mod_)); | ||
|
||
int numOfInterpreter = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The names sounds odd. Maybe numOfInterpreterNodes
and numOfCPUNode
?
Could you also check that it is exactly the kinds of nodes (or the nodes) you expected to be on CPU and Interpreter respectively?
tests/unittests/PartitionerTest.cpp
Outdated
ASSERT_EQ(mod_.getFunctions().size(), 3); | ||
ASSERT_EQ(dagList.size(), 1); | ||
ASSERT_TRUE(checkSaveNode(mod_)); | ||
int numOfInterpreter = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment here about the names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@beicy has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
Summary:
Provide an easier way for using heterogeneous partition : user can define which ops not running on which type of backends.
related to #2814
Documentation:
[Optional Fixes #issue]
Test Plan:
added unittest;
Please see a detailed explanation of how to fill out the fields in the relevant sections in PULL_REQUEST.md.