// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. // // 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. #include "paddle/phi/kernels/dgc_kernel.h" #include #include "dgc/dgc.h" #include "paddle/common/enforce.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/common/memory_utils.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/funcs/broadcast_function.h" #include "paddle/phi/kernels/funcs/eigen/common.h" #include "paddle/phi/kernels/funcs/eigen/eigen_function.h" #include "paddle/phi/kernels/funcs/elementwise_functor.h" #include "paddle/phi/kernels/funcs/math_function.h" namespace phi { inline float get_period_sparcity(const std::vector& sparsity, float cur_step, float rampup_steps) { PADDLE_ENFORCE_GE( static_cast(cur_step), 0, common::errors::InvalidArgument("DGC current step=%d, but it must >= 0, " "please submit issue in github", static_cast(cur_step))); size_t idx = static_cast(cur_step * sparsity.size() / rampup_steps); if (idx >= sparsity.size()) { idx = sparsity.size() - 1; } PADDLE_ENFORCE_LT( idx, sparsity.size(), common::errors::OutOfRange( "sparsity index out of bounds. idx=%d >= sparsity.size=%d", idx, sparsity.size())); return sparsity[idx]; } template void DGCKernel(const Context& dev_ctx, const DenseTensor& u, const DenseTensor& v, const DenseTensor& grad, const DenseTensor& param, const DenseTensor& current_step_tensor, const DenseTensor& nranks_tensor, float m, bool use_nesterov, const std::vector& sparsity, float rampup_begin_step, float rampup_step, float regular_coeff, int regular_type, DenseTensor* u_out, DenseTensor* v_out, DenseTensor* encode_grad_out, DenseTensor* grad_out, DenseTensor* k_out, DenseTensor* gather_buff) { // nranks const int nranks = static_cast(*nranks_tensor.data()); PADDLE_ENFORCE_GT(nranks, 1, common::errors::PreconditionNotMet( "DGC is not useful when num_trainers <= 1. Please " "use multi card or multi machine GPU")); auto param_e = EigenVector::Flatten(param); auto grad_e = EigenVector::Flatten(grad); auto grad_out_e = EigenVector::Flatten(*grad_out); auto& eigen_ctx = *dev_ctx.eigen_device(); // NOTE. In paddle, loss has divided by nranks. Because dgc_op is before // allreduce, so local regular_coeff need div nranks too. But now we // multi grad with nranks in dgc_op, in that case regular_coeff don't // need to /nranks, can prevent precision loss. For coeff often equal // with 1e-4, if nranks=32, coeff/nranks will be 3.125e-6, the numerical // accuracy of coeff/nranks will be too low. PADDLE_ENFORCE_EQ(regular_type >= 0 && regular_type <= 2, true, common::errors::InvalidArgument( "DGC only support one of None|L1Decay|L2Decay " "Regularization for now.")); if (regular_type == 0) { grad_out_e.device(eigen_ctx) = (1.0 * nranks) * grad_e; } else if (regular_type == 1) { // L1Decay. grad = grad + coeff * sign(param) grad_out_e.device(eigen_ctx) = (1.0 * nranks) * grad_e + regular_coeff * param_e.sign(); } else if (regular_type == 2) { // L2Decay. grad = grad + coeff * param grad_out_e.device(eigen_ctx) = (1.0 * nranks) * grad_e + regular_coeff * param_e; } // current step const float* current_step = current_step_tensor.data(); if (static_cast(*current_step) < static_cast(rampup_begin_step)) { VLOG(10) << "current_step:" << *current_step << " < rampup_begin_step:" << rampup_begin_step << " so doesn't use dgc"; return; } float ratio = 1 - get_period_sparcity( sparsity, static_cast(*current_step - rampup_begin_step), rampup_step); PADDLE_ENFORCE_GE( ratio, 0.0, common::errors::InvalidArgument("DGC sparsity ratio must >= 0")); PADDLE_ENFORCE_LT( ratio, 1.0, common::errors::InvalidArgument("DGC sparsity ratio must < 1")); int64_t k_64 = static_cast(grad.numel() * ratio); PADDLE_ENFORCE_LE_INT_MAX(k_64, "dgc k"); int k = static_cast(k_64); VLOG(10) << "m:" << m << ", use_nesterov:" << use_nesterov << ", rampup_begin_step:" << rampup_begin_step << ", rampup_step:" << rampup_step << ", current_step:" << *current_step << ", ratio:" << ratio << ", k:" << k << ", nranks:" << nranks; T* k_out_data = k_out->data(); *k_out_data = k; // FIXME(gongwb): use cublas. auto u_out_e = EigenVector::Flatten(*u_out); auto u_e = EigenVector::Flatten(u); // calc local momentum from global momentum // NOTE. If grad not multi nranks, need add below code. // if (static_cast(*current_step) == // static_cast(rampup_begin_step)) { // u_out_e.device(eigen_ctx) = (1.0 / nranks) * u_e; // } if (use_nesterov) { // u = m * (u + grad) u_out_e.device(eigen_ctx) = m * (u_e + grad_out_e); // v = u + v + grad dev_ctx.template Alloc(v_out); funcs::ElementwiseCompute, T>( dev_ctx, u, v, funcs::AddFunctor(), v_out, 0); funcs::ElementwiseCompute, T>( dev_ctx, grad, v, funcs::AddFunctor(), v_out, 0); } else { // u = m * u + grad u_out_e.device(eigen_ctx) = m * u_e + grad_out_e; // v = u + v dev_ctx.template Alloc(v_out); funcs::ElementwiseCompute, T>( dev_ctx, u, v, funcs::AddFunctor(), v_out, 0); } T* v_out_data = dev_ctx.template Alloc(v_out); T* u_out_data = dev_ctx.template Alloc(u_out); encode_grad_out->Resize(DDim{2 * k}); T* encode_grad_out_data = dev_ctx.template Alloc(encode_grad_out); gather_buff->Resize(DDim{2 * k * nranks}); dev_ctx.template Alloc(gather_buff); int buf_size = paddle::communication::dgc::get_buffer_size(k); Allocator::AllocationPtr tmp_ious_data; #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) if (dev_ctx.GetPlace().GetType() == AllocationType::GPU || dev_ctx.GetPlace().GetType() == AllocationType::CUSTOM) { tmp_ious_data = memory_utils::Alloc( dev_ctx.GetPlace(), buf_size, Stream(reinterpret_cast(dev_ctx.stream()))); } #endif if (dev_ctx.GetPlace().GetType() == AllocationType::CPU) { tmp_ious_data = memory_utils::Alloc(dev_ctx.GetPlace(), buf_size); } void* buf = reinterpret_cast(tmp_ious_data->ptr()); int64_t v_out_numel_64 = v_out->numel(); PADDLE_ENFORCE_LE_INT_MAX(v_out_numel_64, "dgc v_out numel"); int v_out_numel = static_cast(v_out_numel_64); if (!paddle::communication::dgc::k_select( static_cast(encode_grad_out_data), k, v_out_data, v_out_numel, buf, dev_ctx.stream(), u_out_data)) { // TODO(weihang): owner should polish this error message PADDLE_THROW(common::errors::InvalidArgument( "V_out numel error, V_out numel is %d.", v_out->numel())); } funcs::SetConstant tset; tset(dev_ctx, grad_out, static_cast(0)); } } // namespace phi PD_REGISTER_KERNEL(dgc, GPU, ALL_LAYOUT, phi::DGCKernel, float) {}