/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. 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. */ #define EIGEN_USE_GPU #include #include #include #include #include #include #include #include #include "paddle/framework/block_desc.h" #include "paddle/framework/op_desc.h" #include "paddle/framework/op_registry.h" #include "paddle/framework/program_desc.h" #include "paddle/framework/var_desc.h" #include "paddle/operators/math/math_function.h" #include "paddle/operators/nccl/nccl_gpu_common.h" #include "paddle/platform/device_context.h" #include "paddle/platform/enforce.h" #include "paddle/platform/gpu_info.h" #include "paddle/platform/place.h" USE_NO_KERNEL_OP(ncclInit); USE_GPU_ONLY_OP(ncclAllReduce); USE_GPU_ONLY_OP(ncclReduce); USE_GPU_ONLY_OP(ncclBcast); namespace f = paddle::framework; namespace p = paddle::platform; static std::vector gpu_list; // test data amount const f::DDim kDims = {100, 100}; // nccl op common tester, init communicator. class NCCLTester : public ::testing::Test { public: virtual void SetUp() override { cpu_ctx = new p::CPUDeviceContext(p::CPUPlace()); for (size_t i = 0; i < gpu_list.size(); ++i) { p::GPUPlace place(i); dev_ctxs.emplace_back(new p::CUDADeviceContext(place)); } NCCLInitOp(); } virtual void TearDown() override { for (auto &device_context : dev_ctxs) { delete device_context; } } void NCCLInitOp() { std::unique_ptr op1(new f::OpDescBind); op1->SetType("ncclInit"); op1->SetOutput("Communicator", {"comm"}); op1->SetAttr("gpus", {gpu_list}); auto *var = g_scope.Var("comm"); var->GetMutable(); auto op = f::OpRegistry::CreateOp(*op1); VLOG(1) << "invoke NCCLInitOp."; op->Run(g_scope, *cpu_ctx); VLOG(1) << "NCCLInitOp finished."; } template void PerThreadProgram(int gpu_id, const f::OpDescBind &op_desc, f::Scope *scope) { std::unique_lock lk(mu); f::ProgramDescBind program; f::BlockDescBind *block = program.Block(0); f::OpDescBind *op1 = block->AppendOp(); *op1 = op_desc; p::GPUPlace place(gpu_id); auto &ctx = dev_ctxs.at(gpu_id); auto *send_tensor = scope->Var("st")->GetMutable(); auto *recv_tensor = scope->Var("rt")->GetMutable(); send_tensor->Resize(kDims); send_tensor->mutable_data(kDims, place); std::vector send_vector(f::product(kDims), gpu_id); send_tensor->CopyFromVector(send_vector, *ctx); lk.unlock(); PADDLE_ENFORCE(send_tensor->numel() == f::product(kDims), "Tensor numel not match!"); ctx->Wait(); VLOG(1) << "Send Tensor filled with elements " << send_tensor->numel(); auto op = f::OpRegistry::CreateOp(*op1); VLOG(1) << "Device : " << gpu_id << " invoke " << op_desc.Type(); op->Run(*scope, *ctx); VLOG(1) << "Device : " << gpu_id << " finished " << op_desc.Type(); } public: std::vector dev_ctxs; p::DeviceContext *cpu_ctx; f::Scope g_scope; std::mutex mu; }; // ncclInitOp with desc // TEST(NCCL, ncclInitOp) { // std::unique_ptr op_desc(new f::OpDescBind); // op_desc->SetType("ncclInit"); // op_desc->SetOutput("Communicator", {"x1"}); // op_desc->SetAttr("gpus", {gpu_list}); // f::Scope g_scope; // std::unique_ptr ctx(new // p::CPUDeviceContext(p::CPUPlace())); // auto *var = g_scope.Var("x1"); // var->GetMutable(); // auto op = f::OpRegistry::CreateOp(*op_desc); // VLOG(1) << "invoke NCCLInitOp."; // op->Run(g_scope, *ctx.get()); // VLOG(1) << "NCCLInitOp finished."; // } // ncclAllReduceOp with desc // TEST_F(NCCLTester, ncclAllReduceOp) { // std::unique_ptr op2(new f::OpDescBind); // op2->SetType("ncclAllReduce"); // op2->SetInput("X", {"st"}); // op2->SetInput("Communicator", {"comm"}); // op2->SetOutput("Out", {"rt"}); // std::vector dev_scopes; // std::vector ths; // for (size_t i = 0; i < gpu_list.size(); ++i) { // dev_scopes.emplace_back(&g_scope.NewScope()); // std::thread th(&NCCLTester::PerThreadProgram, this, gpu_list[i], // *op2.get(), dev_scopes[i]); // ths.emplace_back(std::move(th)); // } // for (size_t i = 0; i < gpu_list.size(); ++i) { // ths[i].join(); // } // // check results // float result = std::accumulate(gpu_list.begin(), gpu_list.end(), 0); // for (size_t i = 0; i < dev_scopes.size(); ++i) { // p::CPUPlace cpu_place; // p::GPUPlace gpu_place(gpu_list[i]); // auto &recv_tensor = dev_scopes[i]->FindVar("rt")->Get(); // auto *rt = recv_tensor.data(); // auto *result_tensor = // dev_scopes[i]->Var("ct")->GetMutable(); // result_tensor->Resize(kDims); // auto *ct = result_tensor->mutable_data(cpu_place); // paddle::memory::Copy( // cpu_place, ct, p::GPUPlace(gpu_list[i]), rt, // recv_tensor.numel() * sizeof(float), // static_cast(dev_ctxs[i])->stream()); // for (size_t j = 0; j < f::product(kDims); ++j) { // ASSERT_NEAR(ct[j], result, 1e-5); // } // } // } // ncclAReduceOp with desc TEST_F(NCCLTester, ncclReduceOp) { std::unique_ptr op2(new f::OpDescBind); const int kRoot = 0; op2->SetType("ncclReduce"); op2->SetInput("X", {"st"}); op2->SetInput("Communicator", {"comm"}); op2->SetOutput("Out", {"rt"}); op2->SetAttr("root", {kRoot}); std::vector dev_scopes; std::vector ths; for (size_t i = 0; i < gpu_list.size(); ++i) { dev_scopes.emplace_back(&g_scope.NewScope()); std::thread th(&NCCLTester::PerThreadProgram, this, gpu_list[i], *op2.get(), dev_scopes[i]); ths.emplace_back(std::move(th)); } for (size_t i = 0; i < gpu_list.size(); ++i) { ths[i].join(); } // check results on float result = std::accumulate(gpu_list.begin(), gpu_list.end(), 0); p::CPUPlace cpu_place; p::GPUPlace gpu_place(gpu_list[kRoot]); auto &recv_tensor = dev_scopes[kRoot]->FindVar("rt")->Get(); auto *rt = recv_tensor.data(); auto *result_tensor = dev_scopes[kRoot]->Var("ct")->GetMutable(); result_tensor->Resize(kDims); auto *ct = result_tensor->mutable_data(cpu_place); paddle::memory::Copy( cpu_place, ct, p::GPUPlace(gpu_list[kRoot]), rt, recv_tensor.numel() * sizeof(float), static_cast(dev_ctxs[kRoot])->stream()); for (int j = 0; j < f::product(kDims); ++j) { ASSERT_NEAR(ct[j], result, 1e-5); } } // // ncclBcastOp with desc TEST_F(NCCLTester, ncclBcastOp) { std::unique_ptr op2(new f::OpDescBind); const int kRoot = 0; op2->SetType("ncclBcast"); op2->SetInput("X", {"st"}); op2->SetInput("Communicator", {"comm"}); op2->SetOutput("Out", {"rt"}); op2->SetAttr("root", {kRoot}); std::vector dev_scopes; std::vector ths; for (size_t i = 0; i < gpu_list.size(); ++i) { dev_scopes.emplace_back(&g_scope.NewScope()); std::thread th(&NCCLTester::PerThreadProgram, this, gpu_list[i], *op2.get(), dev_scopes[i]); ths.emplace_back(std::move(th)); } for (size_t i = 0; i < gpu_list.size(); ++i) { ths[i].join(); } const int idx = 1; // check results on float result = std::accumulate(gpu_list.begin(), gpu_list.end(), 0); p::CPUPlace cpu_place; p::GPUPlace gpu_place(gpu_list[idx]); auto &recv_tensor = dev_scopes[idx]->FindVar("rt")->Get(); auto *rt = recv_tensor.data(); auto *result_tensor = dev_scopes[idx]->Var("ct")->GetMutable(); result_tensor->Resize(kDims); auto *ct = result_tensor->mutable_data(cpu_place); paddle::memory::Copy( cpu_place, ct, p::GPUPlace(gpu_list[idx]), rt, recv_tensor.numel() * sizeof(float), static_cast(dev_ctxs[idx])->stream()); for (size_t j = 0; j < f::product(kDims); ++j) { ASSERT_NEAR(ct[j], result, 1e-5); } } // joint ncclBcastOp and ncclReduceOp TEST_F(NCCLTester, MultipleOp) { const int kRoot = 0; std::unique_ptr op1(new f::OpDescBind); op1->SetType("ncclReduce"); op1->SetInput("X", {"rt"}); op1->SetInput("Communicator", {"comm"}); op1->SetOutput("Out", {"rt"}); op2->SetAttr("root", {kRoot}); std::unique_ptr op2(new f::OpDescBind); op2->SetType("ncclBcast"); op2->SetInput("X", {"st"}); op2->SetInput("Communicator", {"comm"}); op2->SetOutput("Out", {"rt"}); op2->SetAttr("root", {kRoot}); std::vector dev_scopes; std::vector ths; // run Bcast for (size_t i = 0; i < gpu_list.size(); ++i) { dev_scopes.emplace_back(&g_scope.NewScope()); std::thread th(&NCCLTester::PerThreadProgram, this, gpu_list[i], *op1.get(), dev_scopes[i]); ths.emplace_back(std::move(th)); } for (size_t i = 0; i < gpu_list.size(); ++i) { ths[i].join(); } ths.clear(); // run Reduce for (size_t i = 0; i < gpu_list.size(); ++i) { dev_scopes.emplace_back(&g_scope.NewScope()); std::thread th(&NCCLTester::PerThreadProgram, this, gpu_list[i], *op2.get(), dev_scopes[i]); ths.emplace_back(std::move(th)); } for (size_t i = 0; i < gpu_list.size(); ++i) { ths[i].join(); } // check results float result = std::accumulate(gpu_list.begin(), gpu_list.end(), 0); for (size_t i = 0; i < dev_scopes.size(); ++i) { p::CPUPlace cpu_place; p::GPUPlace gpu_place(gpu_list[i]); auto &recv_tensor = dev_scopes[i]->FindVar("rt")->Get(); auto *rt = recv_tensor.data(); auto *result_tensor = dev_scopes[i]->Var("ct")->GetMutable(); result_tensor->Resize(kDims); auto *ct = result_tensor->mutable_data(cpu_place); paddle::memory::Copy( cpu_place, ct, p::GPUPlace(gpu_list[i]), rt, recv_tensor.numel() * sizeof(float), static_cast(dev_ctxs[i])->stream()); for (int j = 0; j < f::product(kDims); ++j) { ASSERT_NEAR(ct[j], result, 1e-5); } } } int main(int argc, char **argv) { const int dev_count = p::GetCUDADeviceCount(); if (dev_count <= 1) { LOG(WARNING) << "Cannot test multi-gpu nccl, because the CUDA device count is " << dev_count; return 0; } for (int i = 0; i < dev_count; ++i) { gpu_list.emplace_back(i); } testing::InitGoogleTest(&argc, argv); // device context should be release before scope. // otherwise driver will down. return RUN_ALL_TESTS(); }