// Copyright (c) 2024 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. #pragma once #include "paddle/phi/core/dense_tensor.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/funcs/eigen/common.h" #include "paddle/phi/kernels/funcs/for_range.h" #include "paddle/phi/kernels/funcs/selected_rows_functor.h" namespace phi { template class SparseFTRLFunctor { private: const T* g_; const T* p_; const T* s_acc_; const T* l_acc_; const T* lr_; const T l1_; const T l2_; const T lr_power_; const int64_t* rows_; const int64_t row_numel_; T* p_out_; T* s_acc_out_; T* l_acc_out_; public: SparseFTRLFunctor(const T* g, const T* p, const T* s_acc, const T* lr, const T l1, const T l2, const T lr_power, const int64_t* rows, int64_t row_numel, T* p_out, T* s_acc_out, T* l_acc_out) : g_(g), p_(p), s_acc_(s_acc), lr_(lr), l1_(l1), l2_(l2), lr_power_(lr_power), rows_(rows), row_numel_(row_numel), p_out_(p_out), s_acc_out_(s_acc_out), l_acc_out_(l_acc_out) {} inline HOSTDEVICE void operator()(size_t i) { auto j = rows_[i / row_numel_] * row_numel_ + i % row_numel_; const T g = g_[i]; const T p = p_[j]; const T s_acc = s_acc_[j]; const T lr = lr_[0]; auto new_acc = s_acc + g * g; if (lr_power_ == static_cast(-0.5)) { l_acc_out_[j] += g - (std::sqrt(new_acc) - std::sqrt(s_acc)) / lr * p; } else { l_acc_out_[j] += g - (std::pow(new_acc, -lr_power_) - std::pow(s_acc, -lr_power_)) / lr * p; } auto l_acc = l_acc_out_[j]; if (std::fabs(l_acc) > l1_) { auto x = -l_acc; if (l_acc >= static_cast(0)) { x += l1_; } else { x -= l1_; } auto y = static_cast(2) * l2_; if (lr_power_ == static_cast(-0.5)) { y += std::sqrt(new_acc) / lr; } else { y += std::pow(new_acc, -lr_power_) / lr; } auto pre_shrink = x / y; p_out_[j] = pre_shrink; } else { p_out_[j] = static_cast(0); } s_acc_out_[j] += g * g; } }; template void FTRLOpKernel(const Context& dev_ctx, const DenseTensor& param, const DenseTensor& squared_accumulator, const DenseTensor& linear_accumulator, const DenseTensor& grad_in, const DenseTensor& learning_rate, float l1_in, float l2_in, float lr_power_in, DenseTensor* param_out, DenseTensor* squared_accum_out, DenseTensor* linear_accum_out) { auto* lr_in = &learning_rate; auto* param_in = ¶m; auto* sq_accum_in = &squared_accumulator; auto* lin_accum_in = &linear_accumulator; auto* sq_accum_out = squared_accum_out; auto* lin_accum_out = linear_accum_out; dev_ctx.template Alloc(param_out); dev_ctx.template Alloc(sq_accum_out); dev_ctx.template Alloc(lin_accum_out); auto l1 = static_cast(l1_in) + static_cast(1e-10); auto l2 = static_cast(l2_in) + static_cast(1e-10); auto lr_power = static_cast(lr_power_in); auto grad = &grad_in; auto g = EigenVector::Flatten(*grad); auto p = EigenVector::Flatten(*param_in); auto sq_accum = EigenVector::Flatten(*sq_accum_in); auto lin_accum = EigenVector::Flatten(*lin_accum_in); auto lr = EigenVector::Flatten(*lr_in); auto p_out = EigenVector::Flatten(*param_out); auto s_acc_out = EigenVector::Flatten(*sq_accum_out); auto l_acc_out = EigenVector::Flatten(*lin_accum_out); auto& place = *dev_ctx.eigen_device(); Eigen::DSizes grad_dsize(grad->numel()); auto new_accum = sq_accum + g * g; // Special case for lr_power = -0.5 if (lr_power == static_cast(-0.5)) { l_acc_out.device(place) = lin_accum + g - ((new_accum.sqrt() - sq_accum.sqrt()) / lr.broadcast(grad_dsize)) * p; } else { l_acc_out.device(place) = lin_accum + g - ((new_accum.pow(-lr_power) - sq_accum.pow(-lr_power)) / lr.broadcast(grad_dsize)) * p; } auto x = (l_acc_out.constant(l1) * l_acc_out.sign() - l_acc_out); if (lr_power == static_cast(-0.5)) { auto y = (new_accum.sqrt() / lr.broadcast(grad_dsize)) + l_acc_out.constant(static_cast(2) * l2); auto pre_shrink = x / y; p_out.device(place) = (l_acc_out.abs() > l_acc_out.constant(l1)) .select(pre_shrink, p.constant(static_cast(0))); } else { auto y = (new_accum.pow(-lr_power) / lr.broadcast(grad_dsize)) + l_acc_out.constant(static_cast(2) * l2); auto pre_shrink = x / y; p_out.device(place) = (l_acc_out.abs() > l_acc_out.constant(l1)) .select(pre_shrink, p.constant(static_cast(0))); } s_acc_out.device(place) = sq_accum + g * g; } } // namespace phi