Files
paddlepaddle--paddle/paddle/phi/kernels/gpu/dgc_kernel.cu
T
2026-07-13 12:40:42 +08:00

231 lines
8.3 KiB
Plaintext

// 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 <glog/logging.h>
#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<float>& sparsity,
float cur_step,
float rampup_steps) {
PADDLE_ENFORCE_GE(
static_cast<int>(cur_step),
0,
common::errors::InvalidArgument("DGC current step=%d, but it must >= 0, "
"please submit issue in github",
static_cast<int>(cur_step)));
size_t idx = static_cast<int>(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 <typename T, typename Context>
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<float>& 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<int>(*nranks_tensor.data<float>());
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<T>::Flatten(param);
auto grad_e = EigenVector<T>::Flatten(grad);
auto grad_out_e = EigenVector<T>::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<float>();
if (static_cast<int>(*current_step) < static_cast<int>(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<float>(*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<int64_t>(grad.numel() * ratio);
PADDLE_ENFORCE_LE_INT_MAX(k_64, "dgc k");
int k = static_cast<int>(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<T>();
*k_out_data = k;
// FIXME(gongwb): use cublas.
auto u_out_e = EigenVector<T>::Flatten(*u_out);
auto u_e = EigenVector<T>::Flatten(u);
// calc local momentum from global momentum
// NOTE. If grad not multi nranks, need add below code.
// if (static_cast<int>(*current_step) ==
// static_cast<int>(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<T>(v_out);
funcs::ElementwiseCompute<funcs::AddFunctor<T>, T>(
dev_ctx, u, v, funcs::AddFunctor<T>(), v_out, 0);
funcs::ElementwiseCompute<funcs::AddFunctor<T>, T>(
dev_ctx, grad, v, funcs::AddFunctor<T>(), 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<T>(v_out);
funcs::ElementwiseCompute<funcs::AddFunctor<T>, T>(
dev_ctx, u, v, funcs::AddFunctor<T>(), v_out, 0);
}
T* v_out_data = dev_ctx.template Alloc<T>(v_out);
T* u_out_data = dev_ctx.template Alloc<T>(u_out);
encode_grad_out->Resize(DDim{2 * k});
T* encode_grad_out_data = dev_ctx.template Alloc<T>(encode_grad_out);
gather_buff->Resize(DDim{2 * k * nranks});
dev_ctx.template Alloc<T>(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<StreamId>(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<void*>(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<int>(v_out_numel_64);
if (!paddle::communication::dgc::k_select(
static_cast<void*>(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<Context, T> tset;
tset(dev_ctx, grad_out, static_cast<T>(0));
}
} // namespace phi
PD_REGISTER_KERNEL(dgc, GPU, ALL_LAYOUT, phi::DGCKernel, float) {}