/* Copyright (c) 2020 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. Indicesou 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 #ifdef __NVCC__ #include "cub/cub.cuh" #endif #ifdef __HIPCC__ #include namespace cub = hipcub; #endif #include "paddle/fluid/operators/amp/fp16_type_traits.h" #include "paddle/fluid/operators/p_norm_op.h" #include "paddle/fluid/platform/float16.h" namespace paddle { namespace operators { template __device__ __forceinline__ int sgn(T val) { return (T(0) < val) - (val < T(0)); } __device__ __forceinline__ platform::float16 inline_abs(platform::float16 x) { return static_cast(abs(static_cast(x))); } __device__ __forceinline__ float inline_abs(float x) { return abs(x); } __device__ __forceinline__ double inline_abs(double x) { return abs(x); } __device__ __forceinline__ int inline_sign(platform::float16 x) { return sgn(x); } __device__ __forceinline__ int inline_sign(float x) { return sgn(x); } __device__ __forceinline__ int inline_sign(double x) { return sgn(x); } __device__ __forceinline__ platform::float16 inline_pow( platform::float16 base, platform::float16 exponent) { return static_cast( pow(static_cast(base), static_cast(exponent))); } __device__ __forceinline__ float inline_pow(float base, float exponent) { return pow(base, exponent); } __device__ __forceinline__ double inline_pow(double base, double exponent) { return pow(base, exponent); } template __global__ void Pnorm(const T* x, const int pre, const int axis_n, // dim in axis const int post, float porder, T* out_norm) { using MT = typename details::MPTypeTrait::Type; typedef cub::BlockReduce BlockReduce; __shared__ typename BlockReduce::TempStorage temp_storage; int num = pre * post; auto porder_t = static_cast(porder); auto porder_inv = static_cast(1.0 / porder); for (int i = blockIdx.x; i < num; i += gridDim.x) { int base = (i / post) * post * axis_n + (i % post); MT sum = static_cast(0.0); for (int j = threadIdx.x; j < axis_n; j += blockDim.x) { const MT x_ij = static_cast(x[base + j * post]); sum += inline_pow(inline_abs(x_ij), porder_t); } MT reduce_result = BlockReduce(temp_storage).Sum(sum); if (threadIdx.x == 0) out_norm[i] = static_cast(inline_pow(reduce_result, porder_inv)); } } template __global__ void ZeorNorm(const T* x, const int pre, const int axis_n, // dim in axis const int post, T* out_norm) { using MT = typename details::MPTypeTrait::Type; typedef cub::BlockReduce BlockReduce; __shared__ typename BlockReduce::TempStorage temp_storage; int num = pre * post; for (int i = blockIdx.x; i < num; i += gridDim.x) { int base = (i / post) * post * axis_n + (i % post); MT sum = static_cast(0.0); for (int j = threadIdx.x; j < axis_n; j += blockDim.x) { const MT x_ij = static_cast(x[base + j * post]); sum += static_cast(static_cast(x_ij) != 0); } MT reduce_result = BlockReduce(temp_storage).Sum(sum); if (threadIdx.x == 0) out_norm[i] = static_cast(reduce_result); } } template __global__ void InfNorm(const T* x, const int pre, const int axis_n, // dim in axis const int post, T* out_norm) { typedef cub::BlockReduce BlockReduce; __shared__ typename BlockReduce::TempStorage temp_storage; int num = pre * post; for (int i = blockIdx.x; i < num; i += gridDim.x) { int base = (i / post) * post * axis_n + (i % post); T cur_max = inline_abs(x[base]); for (int j = threadIdx.x; j < axis_n; j += blockDim.x) { T x_ij_abs = inline_abs(x[base + j * post]); if (cur_max < x_ij_abs) cur_max = x_ij_abs; } T reduce_result = BlockReduce(temp_storage).Reduce(cur_max, cub::Max()); if (threadIdx.x == 0) out_norm[i] = reduce_result; } } template __global__ void NegInfNorm(const T* x, const int pre, const int axis_n, // dim in axis const int post, T* out_norm) { typedef cub::BlockReduce BlockReduce; __shared__ typename BlockReduce::TempStorage temp_storage; int num = pre * post; for (int i = blockIdx.x; i < num; i += gridDim.x) { int base = (i / post) * post * axis_n + (i % post); T cur_min = inline_abs(x[base]); for (int j = threadIdx.x; j < axis_n; j += blockDim.x) { T x_ij_abs = inline_abs(x[base + j * post]); if (cur_min > x_ij_abs) cur_min = x_ij_abs; } T reduce_result = BlockReduce(temp_storage).Reduce(cur_min, cub::Min()); if (threadIdx.x == 0) out_norm[i] = reduce_result; } } template class PnormCUDAKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& ctx) const override { auto* in_x = ctx.Input("X"); auto* out_norm = ctx.Output("Out"); const T* x = in_x->data(); T* norm = out_norm->mutable_data(ctx.GetPlace()); auto xdim = in_x->dims(); auto ndim = out_norm->dims(); float porder = ctx.Attr("porder"); int axis = ctx.Attr("axis"); bool asvector = ctx.Attr("asvector"); if (axis < 0) axis = xdim.size() + axis; int pre, n, post; GetDims(xdim, axis, &pre, &n, &post, asvector); auto& dev_ctx = ctx.cuda_device_context(); #ifdef __HIPCC__ const int block = 256; #else const int block = 512; #endif int max_threads = dev_ctx.GetMaxPhysicalThreadCount(); const int max_blocks = std::max(max_threads / block, 1); int grid = std::min(max_blocks, pre * post); if (porder == 0) { ZeorNorm<<>>(x, pre, n, post, norm); } else if (porder == INFINITY) { InfNorm<<>>(x, pre, n, post, norm); } else if (porder == -INFINITY) { NegInfNorm<<>>(x, pre, n, post, norm); } else { Pnorm<<>>(x, pre, n, post, porder, norm); } } }; template __global__ void PnormGradient(const T* x, const T* x_norm, const T* y_grad, const float porder, const int pre, const int axis_n, const int post, const T eps, T* x_grad) { using MT = typename details::MPTypeTrait::Type; // dx = (x/pnorm_broadcast).pow(p-1) * norm_dy.broadcast * sign(x) int num = pre * post; auto porder_grad = static_cast(porder - 1.0f); for (int i = blockIdx.x; i < num; i += gridDim.x) { __shared__ MT pnorm_i; __shared__ MT yout_i; auto base = (i / post) * post * axis_n + (i % post); if (threadIdx.x == 0) { pnorm_i = static_cast(x_norm[i]); yout_i = static_cast(y_grad[i]); } __syncthreads(); for (int j = threadIdx.x; j < axis_n; j += blockDim.x) { int index = base + j * post; const MT x_ij = static_cast(inline_abs(x[index])); x_grad[index] = static_cast( inline_pow(x_ij, porder_grad) / (inline_pow(pnorm_i, porder_grad) + static_cast(eps)) * yout_i * static_cast(inline_sign(x[index]))); } } } template __global__ void InfNormGradient(const T* x, const T* x_norm, const T* y_grad, const int pre, const int axis_n, const int post, T* x_grad) { int num = pre * post; for (int i = blockIdx.x; i < num; i += gridDim.x) { __shared__ T pnorm_i; __shared__ T yout_i; auto base = (i / post) * post * axis_n + (i % post); if (threadIdx.x == 0) { pnorm_i = x_norm[i]; yout_i = y_grad[i]; } __syncthreads(); for (int j = threadIdx.x; j < axis_n; j += blockDim.x) { int index = base + j * post; const T x_ij = inline_abs(x[index]); if (x_ij == pnorm_i) { x_grad[index] = static_cast(inline_sign(x[index])) * yout_i; } else { x_grad[index] = static_cast(0); } } } } template class PnormGradCUDAKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& ctx) const override { auto* in_x = ctx.Input("X"); auto* in_norm = ctx.Input("Out"); auto* in_norm_dy = ctx.Input(framework::GradVarName("Out")); auto* out_dx = ctx.Output(framework::GradVarName("X")); T* dx = out_dx->mutable_data(ctx.GetPlace()); const T* x = in_x->data(); const T* x_norm = in_norm->data(); const T* norm_dy = in_norm_dy->data(); auto xdim = in_x->dims(); float porder = ctx.Attr("porder"); T eps = static_cast(ctx.Attr("epsilon")); int axis = ctx.Attr("axis"); bool asvector = ctx.Attr("asvector"); if (axis < 0) axis = xdim.size() + axis; int pre, n, post; GetDims(xdim, axis, &pre, &n, &post, asvector); auto& dev_ctx = ctx.cuda_device_context(); #ifdef __HIPCC__ const int block = 256; #else const int block = 512; #endif int max_threads = dev_ctx.GetMaxPhysicalThreadCount(); const int max_blocks = std::max(max_threads / block, 1); int grid = std::min(max_blocks, pre * post); if (porder == 0) { math::SetConstant set_zero; auto& dev_ctx = ctx.template device_context(); set_zero(dev_ctx, out_dx, static_cast(0)); } else if (porder == INFINITY || porder == -INFINITY) { InfNormGradient<<>>( x, x_norm, norm_dy, pre, n, post, dx); } else { PnormGradient<<>>( x, x_norm, norm_dy, porder, pre, n, post, eps, dx); } } }; } // namespace operators } // namespace paddle namespace ops = paddle::operators; using CUDA = paddle::platform::CUDADeviceContext; REGISTER_OP_CUDA_KERNEL(p_norm, ops::PnormCUDAKernel, ops::PnormCUDAKernel, ops::PnormCUDAKernel); REGISTER_OP_CUDA_KERNEL( p_norm_grad, ops::PnormGradCUDAKernel, ops::PnormGradCUDAKernel, ops::PnormGradCUDAKernel);