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

2062 lines
78 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Copyright (c) 2021 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 <iostream>
#include "glog/logging.h"
#include "paddle/common/ddim.h"
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/backends/gpu/gpu_device_function.h"
#include "paddle/phi/backends/gpu/gpu_dnn.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/kernels/funcs/aligned_vector.h"
#include "paddle/phi/kernels/funcs/cub.h"
#include "paddle/phi/kernels/funcs/fake_quantize_functor.h"
#include "paddle/phi/kernels/funcs/fast_ln_v1.h"
namespace phi {
namespace funcs {
template <typename T>
using CudnnDataType = phi::backends::gpu::CudnnDataType<T>;
template <typename T>
using LayerNormParamType = typename CudnnDataType<T>::BatchNormParamType;
inline static int GetDesiredBlockDim(int64_t block_dim) {
const int kMaxBlockDim = 512;
#ifdef __HIPCC__
const int lwarpSize = 64;
#else
const int lwarpSize = 32;
#endif
return block_dim >= kMaxBlockDim ? kMaxBlockDim : lwarpSize;
}
static dim3 GetDesiredGridDim(int64_t grid_size) {
dim3 grid_dim(1, 1);
int64_t grid_x = grid_size > 1 ? grid_size : 1;
int64_t grid_y = 1;
if (grid_x > 2147483648LL) {
grid_y = 1024;
grid_x = (grid_x + grid_y - 1) / grid_y;
PADDLE_ENFORCE_LE_INT_MAX(grid_x, "grid_x");
}
grid_dim.x = static_cast<uint32_t>(grid_x);
grid_dim.y = static_cast<uint32_t>(grid_y);
return grid_dim;
}
template <typename U>
static __forceinline__ __device__ U WarpReduceSum(U val) {
unsigned mask = 0u;
CREATE_SHFL_MASK(mask, true);
for (int offset = warpSize / 2; offset > 0; offset /= 2) {
val += phi::backends::gpu::CudaShuffleDownSync(mask, val, offset);
}
return val;
}
template <typename U>
__forceinline__ __device__ U BlockReduceSum(U val, U *shared) {
int lane = threadIdx.x % warpSize;
int wid = threadIdx.x / warpSize;
val = WarpReduceSum(val); // Each warp performs partial reduction
if (lane == 0) shared[wid] = val; // Write reduced value to shared memory
__syncthreads(); // Wait for all partial reductions
// read from shared memory only if that warp existed
val =
(threadIdx.x < blockDim.x / warpSize) ? shared[lane] : static_cast<U>(0);
if (wid == 0) val = WarpReduceSum(val); // Final reduce within first warp
return val;
}
#define FIXED_BLOCK_DIM_CASE_BASE(log2_block_dim, ...) \
case (1 << (log2_block_dim)): { \
constexpr auto kBlockDim = (1 << (log2_block_dim)); \
__VA_ARGS__; \
} break
#define FIXED_BLOCK_DIM_CASE(...) \
FIXED_BLOCK_DIM_CASE_BASE(9, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_CASE_BASE(8, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_CASE_BASE(7, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_CASE_BASE(6, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_CASE_BASE(5, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_CASE_BASE(4, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_CASE_BASE(3, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_CASE_BASE(2, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_CASE_BASE(1, ##__VA_ARGS__)
#define FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
log2_block_dim, feature_size, kMaxBlockNum, ...) \
case (1 << (log2_block_dim)): { \
for (int64_t i = 0; i < std::ceil(feature_size / (1.0 * kMaxBlockNum)); \
i++) { \
int64_t col_offset = i * static_cast<int64_t>(kMaxBlockNum); \
int block_num = static_cast<int>(std::min( \
feature_size - col_offset, static_cast<int64_t>(kMaxBlockNum))); \
constexpr auto kBlockDim = (1 << (log2_block_dim)); \
__VA_ARGS__; \
} \
} break
#define FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE(feature_size, kMaxBlockNum, ...) \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
9, feature_size, kMaxBlockNum, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
8, feature_size, kMaxBlockNum, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
7, feature_size, kMaxBlockNum, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
6, feature_size, kMaxBlockNum, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
5, feature_size, kMaxBlockNum, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
4, feature_size, kMaxBlockNum, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
3, feature_size, kMaxBlockNum, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
2, feature_size, kMaxBlockNum, ##__VA_ARGS__); \
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE_BASE( \
1, feature_size, kMaxBlockNum, ##__VA_ARGS__)
static __device__ __forceinline__ float real_sqrt(float x) { return sqrtf(x); }
static __device__ __forceinline__ double real_sqrt(double x) {
return ::sqrt(x);
}
template <typename T>
struct PairForLayerNorm {
__device__ __forceinline__ PairForLayerNorm() {}
__device__ __forceinline__ PairForLayerNorm(const T &first, const T &second)
: first_(first), second_(second) {}
T first_;
T second_;
};
template <typename T>
struct PairForLayerNormAddFunctor {
__device__ __forceinline__ PairForLayerNorm<T> operator()(
const PairForLayerNorm<T> &p1, const PairForLayerNorm<T> &p2) {
return PairForLayerNorm<T>(p1.first_ + p2.first_, p1.second_ + p2.second_);
}
};
template <typename T>
__inline__ __device__ T rsqrt_(const T val) {
return static_cast<T>(1) / sqrt(val);
}
template <>
__inline__ __device__ float rsqrt_(const float val) {
return rsqrtf(val);
}
template <>
__inline__ __device__ double rsqrt_(const double val) {
return ::rsqrt(val);
}
#if CUDA_ARCH_FP16_SUPPORTED(__CUDA_ARCH__) || defined(PADDLE_WITH_HIP)
template <>
__inline__ __device__ half rsqrt_(const half val) {
return hrsqrt(val);
}
#endif
// fast_ln_v1_fwd_kernel is moved to paddle/phi/kernels/funcs/fast_ln_v1.h
template <typename T>
__forceinline__ __device__ int8_t quant_helper(const T input,
const float scale,
const int round_type,
const float max_bound,
const float min_bound) {
float quant_value = max_bound * scale * static_cast<float>(input);
if (round_type == 0) {
quant_value = static_cast<float>(roundWithTiesToEven(quant_value));
} else {
quant_value = static_cast<float>(round(quant_value));
}
quant_value = quant_value > max_bound ? max_bound : quant_value;
quant_value = quant_value < min_bound ? min_bound : quant_value;
return static_cast<int8_t>(quant_value);
}
template <typename T, typename U, bool ScaleBiasWithSameTypeX>
using LayerNormScaleBiasT =
typename std::conditional<ScaleBiasWithSameTypeX, T, U>::type;
template <typename T,
typename U,
int BlockDim,
bool ScaleBiasWithSameTypeX = false,
typename InType = T,
typename OutType = T>
__global__ void LayerNormForward(
const InType *x,
const LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *scale,
const LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *bias,
OutType *y,
U *mean,
U *var,
float epsilon,
int64_t feature_size,
const float *dequant_out_scale_data = nullptr,
const int quant_out_scale_offset = 0,
const float quant_in_scale = 1.0,
const int quant_round_type = 1,
const float quant_max_bound = 127.0,
const float quant_min_bound = -127.0) {
__shared__ U mean_share;
__shared__ U var_share;
__shared__ U shared_mean[32]; // threadIdx.x / warpSize <= kMaxBlockDim /
// warpSize <= 1024/32 = 32;
__shared__ U shared_var[32];
int64_t beg_idx =
(static_cast<int64_t>(blockIdx.x) * gridDim.y + blockIdx.y) *
feature_size +
threadIdx.x;
int64_t end_idx =
(static_cast<int64_t>(blockIdx.x) * gridDim.y + blockIdx.y + 1) *
feature_size;
// Step 1: Reduce to calculate mean and var
U mean_val = 0;
U var_val = 0;
for (int64_t i = beg_idx; i < end_idx; i += BlockDim) {
U tmp = static_cast<U>(x[i]);
mean_val += tmp;
var_val += (tmp * tmp);
}
mean_val = BlockReduceSum<U>(mean_val, shared_mean);
var_val = BlockReduceSum<U>(var_val, shared_var);
if (threadIdx.x == 0) {
auto scale = static_cast<U>(static_cast<float>(1.) /
static_cast<float>(feature_size));
auto tmp = mean_val * scale;
mean[blockIdx.x] = mean_share = static_cast<U>(tmp);
var_share = static_cast<U>(var_val * scale - mean_share * mean_share);
var_share = var_share > U(0) ? var_share : U(0);
var[blockIdx.x] = var_share;
}
__syncthreads();
mean_val = mean_share;
U invvar = rsqrt_<U>(var_share + static_cast<U>(epsilon));
// Step 2: Calculate y
if (scale != nullptr) {
if (bias != nullptr) {
for (int64_t i = beg_idx, j = threadIdx.x; i < end_idx;
i += BlockDim, j += BlockDim) {
if (std::is_same<OutType, int8_t>::value) {
y[i] = quant_helper(
static_cast<T>(static_cast<U>(scale[j]) *
(static_cast<U>(x[i]) - mean_val) * invvar +
static_cast<U>(bias[j])),
quant_in_scale,
quant_round_type,
quant_max_bound,
quant_min_bound);
} else {
y[i] = static_cast<OutType>(static_cast<U>(scale[j]) *
(static_cast<U>(x[i]) - mean_val) *
invvar +
static_cast<U>(bias[j]));
}
}
} else {
for (int64_t i = beg_idx, j = threadIdx.x; i < end_idx;
i += BlockDim, j += BlockDim) {
if (std::is_same<OutType, int8_t>::value) {
y[i] = quant_helper(
static_cast<T>(static_cast<U>(scale[j]) *
(static_cast<U>(x[i]) - mean_val) * invvar),
quant_in_scale,
quant_round_type,
quant_max_bound,
quant_min_bound);
} else {
y[i] =
static_cast<OutType>(static_cast<U>(scale[j]) *
(static_cast<U>(x[i]) - mean_val) * invvar);
}
}
}
} else { // scale == nullptr
if (bias != nullptr) {
for (int64_t i = beg_idx, j = threadIdx.x; i < end_idx;
i += BlockDim, j += BlockDim) {
if (std::is_same<OutType, int8_t>::value) {
y[i] = quant_helper(
static_cast<T>((static_cast<U>(x[i]) - mean_val) * invvar +
static_cast<U>(bias[j])),
quant_in_scale,
quant_round_type,
quant_max_bound,
quant_min_bound);
} else {
y[i] =
static_cast<OutType>((static_cast<U>(x[i]) - mean_val) * invvar +
static_cast<U>(bias[j]));
}
}
} else {
for (int64_t i = beg_idx, j = threadIdx.x; i < end_idx;
i += BlockDim, j += BlockDim) {
if (std::is_same<OutType, int8_t>::value) {
y[i] = quant_helper(
static_cast<T>((static_cast<U>(x[i]) - mean_val) * invvar),
quant_in_scale,
quant_round_type,
quant_max_bound,
quant_min_bound);
} else {
y[i] =
static_cast<OutType>((static_cast<U>(x[i]) - mean_val) * invvar);
}
}
}
}
}
template <typename T, typename U, int VPT>
__inline__ __device__ void cuLoadAddStridedInputs(const int64_t i1_block,
const int thr_load_row_off,
const int thr_load_col_off,
const int i2_off,
const int row_stride,
U *warp_buf1,
U *warp_buf2,
const T *__restrict__ input,
const T *__restrict__ dout,
const int64_t i1_end,
const int64_t n2,
const U *__restrict__ mean,
const U *__restrict__ var,
const float epsilon) {
const int64_t i1 = i1_block + thr_load_row_off;
if (i1 >= i1_end) return;
U curr_mean = mean[i1];
U curr_invvar = rsqrt_<U>(var[i1] + epsilon);
#pragma unroll
for (int k = 0; k < VPT; ++k) {
const int i2 = i2_off + k;
const int64_t load_idx = i1 * n2 + i2;
const int64_t write_idx =
static_cast<int64_t>(thr_load_row_off) * row_stride + thr_load_col_off +
k;
if (i2 < n2) {
U curr_input = static_cast<U>(input[load_idx]);
U curr_dout = static_cast<U>(dout[load_idx]);
warp_buf1[write_idx] += curr_dout;
warp_buf2[write_idx] +=
curr_dout * (curr_input - curr_mean) * curr_invvar;
}
}
}
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
template <bool IsFusedDropoutResidualLn,
bool NeedDDropoutSrcPtr,
typename T,
typename U,
typename ScaleT = U,
typename MaskType = uint8_t,
int VecSize = 8,
int WARPS_M = 4,
int WARPS_N = 1,
int BYTES_PER_LDG = 16,
int ELTS_PER_ROW = 1024,
int THREADS_PER_WARP = 32,
int THREADS_PER_ROW = WARPS_N *THREADS_PER_WARP,
int THREADS_PER_CTA = WARPS_M *THREADS_PER_ROW,
int ROWS_PER_CTA = WARPS_M,
int ELTS_PER_ROW_PER_CTA = THREADS_PER_ROW *VecSize,
int LDGS = ELTS_PER_ROW / ELTS_PER_ROW_PER_CTA>
__global__ __launch_bounds__(THREADS_PER_CTA) void fused_ln_bwd_fast_kernel(
const int rows,
float epsilon,
const T *__restrict__ x_ptr,
const ScaleT *__restrict__ gamma_ptr,
const U *__restrict__ mean_ptr,
const U *__restrict__ var_ptr,
const T *__restrict__ dout_ptr,
U *__restrict__ dgamma_temp_ptr,
U *__restrict__ dbeta_temp_ptr,
T *__restrict__ dx_ptr,
const MaskType *mask_ptr = nullptr,
T factor = static_cast<T>(0),
T *d_dropout_src_ptr = nullptr) {
static_assert(
!IsFusedDropoutResidualLn || NeedDDropoutSrcPtr,
"When IsFusedDropoutResidualLn = true, NeedDDropoutSrcPtr must be true.");
using Vec = AlignedVector<T, VecSize>;
using Vec_scale = AlignedVector<ScaleT, VecSize>;
using MaskLoadT = AlignedVector<MaskType, VecSize>;
const int64_t tidx = threadIdx.x;
const int64_t bidx = blockIdx.x;
const int64_t lane = tidx % THREADS_PER_WARP; // 0, 1, ..., 31
const int64_t warp = tidx / THREADS_PER_WARP; // 0, 1, 2, 3
const int64_t warp_m = warp / WARPS_N; // 0, 1, 2, 3
const int64_t warp_n = warp % WARPS_N; // 0
const int64_t tid_r = warp_n * THREADS_PER_WARP + lane; // 0, 1, ..., 31
const int64_t r = bidx * ROWS_PER_CTA + warp_m;
const int64_t c = warp_n * THREADS_PER_WARP + lane;
static_assert(ELTS_PER_ROW == THREADS_PER_ROW * LDGS * VecSize, "");
// smem for column reduction
__shared__ U smem_[ROWS_PER_CTA * ELTS_PER_ROW];
U dgamma_sum[LDGS * VecSize];
U dbeta_sum[LDGS * VecSize];
memset(dgamma_sum, 0, sizeof(U) * LDGS * VecSize);
memset(dbeta_sum, 0, sizeof(U) * LDGS * VecSize);
// Note: it is no use for WARP_N = 1
__shared__ U smem_sum_loss1[ROWS_PER_CTA * WARPS_N]; // 4
__shared__ U smem_sum_loss2[ROWS_PER_CTA * WARPS_N]; // 4
U *sum_loss1_shared = &smem_sum_loss1[warp_m * WARPS_N];
U *sum_loss2_shared = &smem_sum_loss2[warp_m * WARPS_N];
// step-1: compute dx and local results of dscale and dbias
constexpr float rn = 1.f / static_cast<float>(ELTS_PER_ROW);
Vec_scale gamma[LDGS];
int64_t col = c;
#pragma unroll
for (int64_t it = 0; it < LDGS; it++) {
phi::Load<ScaleT, VecSize>(gamma_ptr + col * VecSize, &gamma[it]);
col += THREADS_PER_ROW;
}
#pragma unroll 1
for (int64_t row = r; row < rows; row += gridDim.x * ROWS_PER_CTA) {
const U mean_cur_row = mean_ptr[row];
const U var_cur_row = rsqrt_<U>(var_ptr[row] + epsilon);
Vec dout[LDGS], x[LDGS];
MaskLoadT mask_vec[LDGS];
int64_t col = c;
#pragma unroll
for (int64_t it = 0; it < LDGS; it++) {
phi::Load<T, VecSize>(dout_ptr + row * ELTS_PER_ROW + col * VecSize,
&dout[it]);
phi::Load<T, VecSize>(x_ptr + row * ELTS_PER_ROW + col * VecSize, &x[it]);
if (IsFusedDropoutResidualLn) {
phi::Load<MaskType, VecSize>(
mask_ptr + row * ELTS_PER_ROW + col * VecSize, &mask_vec[it]);
}
col += THREADS_PER_ROW;
}
// local reductions
U dy[LDGS * VecSize];
U y[LDGS * VecSize];
U sum_loss1 = 0.f;
U sum_loss2 = 0.f;
#pragma unroll
for (int it = 0; it < LDGS; it++) {
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
U x_tmp = static_cast<U>(x[it][jt]);
U y_tmp = var_cur_row * (x_tmp - mean_cur_row);
U dy_tmp = static_cast<U>(gamma[it][jt]) *
static_cast<U>(dout[it][jt]); // scale * dy
U dout_tmp = static_cast<U>(dout[it][jt]); // dy
// used for get dx (row reduction)
sum_loss1 += dy_tmp; // scale * dy, sum_1
sum_loss2 += dy_tmp * y_tmp; // scale * dy * y, sum_2
dy[it * VecSize + jt] = dy_tmp; // scale * dy
y[it * VecSize + jt] = y_tmp; // y
// used for get dscale and dbias (column reduction)
dgamma_sum[it * VecSize + jt] += dout_tmp * y_tmp; // dy * y
dbeta_sum[it * VecSize + jt] += dout_tmp; // dy
}
}
// reduction across row for sum_loss1, sum_loss2
if (WARPS_N == 1) {
#pragma unroll
// row reduction among 32 threads.
for (int it = 1; it < THREADS_PER_WARP; it *= 2) {
#ifdef PADDLE_WITH_HIP
sum_loss1 += __shfl_xor(sum_loss1, it);
sum_loss2 += __shfl_xor(sum_loss2, it);
#else
sum_loss1 += __shfl_xor_sync(uint32_t(-1), sum_loss1, it);
sum_loss2 += __shfl_xor_sync(uint32_t(-1), sum_loss2, it);
#endif
}
sum_loss1 *= rn;
sum_loss2 *= rn;
} else {
#pragma unroll
for (int it = 16; it > 0; it /= 2) {
#ifdef PADDLE_WITH_HIP
sum_loss1 += __shfl_down(sum_loss1, it);
sum_loss2 += __shfl_down(sum_loss2, it);
#else
sum_loss1 += __shfl_down_sync(uint32_t(-1), sum_loss1, it);
sum_loss2 += __shfl_down_sync(uint32_t(-1), sum_loss2, it);
#endif
}
if (lane == 0) {
sum_loss1_shared[warp_n] = sum_loss1;
sum_loss2_shared[warp_n] = sum_loss2;
}
__syncthreads();
if (warp_n == 0 && lane == 0) {
sum_loss1 = 0.f;
sum_loss2 = 0.f;
for (int it = 0; it < WARPS_N; it++) {
sum_loss1 += sum_loss1_shared[it];
sum_loss2 += sum_loss2_shared[it];
}
sum_loss1_shared[0] = sum_loss1;
sum_loss2_shared[0] = sum_loss2;
}
__syncthreads();
sum_loss1 = sum_loss1_shared[0] * rn;
sum_loss2 = sum_loss2_shared[0] * rn;
}
#pragma unroll
for (int it = 0; it < LDGS; it++) {
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
U dy_tmp = dy[it * VecSize + jt]; // scale * dy
U y_tmp = y[it * VecSize + jt]; // y
// dx = var * (scale * dy - sum_loss2 * y - sum_loss1)
U dx_tmp = var_cur_row * (dy_tmp - sum_loss2 * y_tmp - sum_loss1);
// Note: reuse x and dout vec register to store dx and d_dropout_src.
x[it][jt] = static_cast<T>(dx_tmp);
if (IsFusedDropoutResidualLn) {
if (factor == static_cast<T>(1.0f)) { // no dropout
dout[it][jt] = x[it][jt] * factor;
} else {
dout[it][jt] =
x[it][jt] * static_cast<T>(mask_vec[it][jt]) * factor;
}
}
}
}
// store dx to global memory
col = c;
#pragma unroll
for (int it = 0; it < LDGS; it++) {
phi::Store<T, VecSize>(x[it],
dx_ptr + row * ELTS_PER_ROW + col * VecSize);
if (IsFusedDropoutResidualLn) {
phi::Store<T, VecSize>(
dout[it], d_dropout_src_ptr + row * ELTS_PER_ROW + col * VecSize);
} else if (NeedDDropoutSrcPtr) {
phi::Store<T, VecSize>(
x[it], d_dropout_src_ptr + row * ELTS_PER_ROW + col * VecSize);
}
col += THREADS_PER_ROW;
}
}
// step-2: column reduction of dscale and dbias for each thread block.
// each block's sum: [4 * 1024] -> [1 * 1024]
enum { NUM_RES = ELTS_PER_ROW / THREADS_PER_CTA }; // 1024/128 = 8
static_assert(NUM_RES * THREADS_PER_CTA == ELTS_PER_ROW, "");
U *smem_write;
smem_write = &smem_[warp_m * ELTS_PER_ROW + tid_r * VecSize]; // [4 * 1024]
#pragma unroll
for (int it = 0; it < LDGS; it++) {
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
smem_write[jt] = dbeta_sum[it * VecSize + jt];
}
smem_write += THREADS_PER_ROW * VecSize; // 32*8
}
__syncthreads();
U cta_dbeta_sum[NUM_RES];
memset(cta_dbeta_sum, 0, sizeof(U) * NUM_RES);
// column reduction for elems in smem: 4*1024 -> 1*1024.
for (int it = 0; it < ROWS_PER_CTA; it++) {
for (int jt = 0; jt < NUM_RES; jt++) {
cta_dbeta_sum[jt] +=
smem_[it * ELTS_PER_ROW + tidx + jt * THREADS_PER_CTA];
}
}
__syncthreads();
smem_write = &smem_[warp_m * ELTS_PER_ROW + tid_r * VecSize];
#pragma unroll
for (int it = 0; it < LDGS; it++) {
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
smem_write[jt] = dgamma_sum[it * VecSize + jt];
}
smem_write += THREADS_PER_ROW * VecSize;
}
__syncthreads();
U cta_dgamma_sum[NUM_RES];
memset(cta_dgamma_sum, 0, sizeof(U) * NUM_RES);
for (int it = 0; it < ROWS_PER_CTA; it++) {
for (int jt = 0; jt < NUM_RES; jt++) {
cta_dgamma_sum[jt] +=
smem_[it * ELTS_PER_ROW + tidx + jt * THREADS_PER_CTA];
}
}
// the shape of results(#blocks, 1024)
U *dgamma_part =
static_cast<U *>(dgamma_temp_ptr) + bidx * ELTS_PER_ROW + tidx;
for (int jt = 0; jt < NUM_RES; jt++) {
*dgamma_part = cta_dgamma_sum[jt];
dgamma_part += THREADS_PER_CTA;
}
U *dbeta_part = static_cast<U *>(dbeta_temp_ptr) + bidx * ELTS_PER_ROW + tidx;
for (int jt = 0; jt < NUM_RES; jt++) {
*dbeta_part = cta_dbeta_sum[jt];
dbeta_part += THREADS_PER_CTA;
}
}
/* This function carry out column reduction whose input is [rows, 1024] and
* output is [1, 1024].
* #blocks: 32
* #threads: 512
*/
// todo(@limin29): to think if there are better impl strategies
template <typename U,
typename ScaleT = U,
int VecSize = 1,
int WARPS_M = 16,
int WARPS_N = 1,
int BYTES_PER_LDG = 4,
int ELTS_PER_ROW = 1024,
int THREADS_PER_WARP = 32,
int THREADS_PER_ROW = WARPS_N *THREADS_PER_WARP,
int THREADS_PER_CTA = WARPS_M *THREADS_PER_ROW,
int ROWS_PER_CTA = WARPS_M,
int ELTS_PER_ROW_PER_CTA = THREADS_PER_ROW *VecSize,
int LDGS = ELTS_PER_ROW / ELTS_PER_ROW_PER_CTA,
int VEC_COLS = ELTS_PER_ROW / VecSize>
__global__ __launch_bounds__(THREADS_PER_CTA) void ln_bwd_fast_final_kernel(
const int rows,
U *__restrict__ dg_part_,
U *__restrict__ db_part_,
ScaleT *__restrict__ dg_,
ScaleT *__restrict__ db_) {
using Vec = AlignedVector<U, VecSize>;
static_assert(VEC_COLS == ELTS_PER_ROW / VecSize, "");
const int tidx = threadIdx.x;
const int bidx = blockIdx.x;
const int lane = tidx % THREADS_PER_WARP;
const int warp = tidx / THREADS_PER_WARP;
const int warp_m = warp / WARPS_N;
const int warp_n = warp % WARPS_N;
const int tid_c = warp_n * THREADS_PER_WARP + lane;
const int c = bidx * THREADS_PER_ROW + tid_c;
const int r = warp_m;
__shared__ U smem_space[(WARPS_M - 1) * THREADS_PER_ROW * VecSize];
for (int64_t col = c; col < VEC_COLS; col += gridDim.x * THREADS_PER_ROW) {
const U *dg_part_ptr = (dg_part_) + r * ELTS_PER_ROW + col * VecSize;
const U *db_part_ptr = (db_part_) + r * ELTS_PER_ROW + col * VecSize;
U dg_sum[VecSize];
U db_sum[VecSize];
memset(dg_sum, 0, sizeof(U) * VecSize);
memset(db_sum, 0, sizeof(U) * VecSize);
#pragma unroll
for (int64_t row = r; row < rows; row += ROWS_PER_CTA) {
Vec dg;
Vec db;
phi::Load<U, VecSize>(dg_part_ptr, &dg);
phi::Load<U, VecSize>(db_part_ptr, &db);
dg_part_ptr += ROWS_PER_CTA * ELTS_PER_ROW;
db_part_ptr += ROWS_PER_CTA * ELTS_PER_ROW;
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
dg_sum[jt] += dg[jt];
db_sum[jt] += db[jt];
}
}
// reduction across rows of the thread block
U *smem_write;
smem_write = smem_space + (warp_m - 1) * THREADS_PER_ROW * VecSize + tid_c;
if (warp_m > 0) {
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
*smem_write = dg_sum[jt];
smem_write += THREADS_PER_ROW;
}
}
__syncthreads();
U *smem_read;
smem_read = smem_space + tid_c;
if (warp_m == 0) {
#pragma unroll
for (int it = 0; it < WARPS_M - 1; it++) {
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
dg_sum[jt] += *smem_read;
smem_read += THREADS_PER_ROW;
}
}
}
__syncthreads();
smem_write = smem_space + (warp_m - 1) * THREADS_PER_ROW * VecSize + tid_c;
if (warp_m > 0) {
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
*smem_write = db_sum[jt];
smem_write += THREADS_PER_ROW;
}
}
__syncthreads();
smem_read = smem_space + tid_c;
if (warp_m == 0) {
#pragma unroll
for (int it = 0; it < WARPS_M - 1; it++) {
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
db_sum[jt] += *smem_read;
smem_read += THREADS_PER_ROW;
}
}
union {
ScaleT raw;
ScaleT elt[VecSize];
} dg_out, db_out;
#pragma unroll
for (int jt = 0; jt < VecSize; jt++) {
dg_out.elt[jt] = dg_sum[jt];
db_out.elt[jt] = db_sum[jt];
}
ScaleT *dg_ptr = reinterpret_cast<ScaleT *>(dg_) + col;
ScaleT *db_ptr = reinterpret_cast<ScaleT *>(db_) + col;
*dg_ptr = dg_out.raw;
*db_ptr = db_out.raw;
}
}
}
/* This function support two kinds of computations (only for float and fp16
* type):
*
* Case-1: compute layer_norm_grad for layernorm op by setting mask_ptr and
* d_dropout_src_ptr to nullptr. Here, d_x_ptr returns the grad of layernorm
* input.
*
* Case-2: compute layer_norm_grad + residual_grad + dropout_grad for
* fused_dropout_residual_layernorm op. Here, dx_ptr returns residual_grad.
*
*/
template <typename T,
typename U,
typename ScaleT = U,
typename MaskType = uint8_t>
void ln_bwd_fast_kernel_driver(const GPUContext &dev_ctx,
const int64_t rows,
const int64_t cols,
float epsilon,
const T *x_ptr,
const ScaleT *scale_ptr,
const U *mean_ptr,
const U *var_ptr,
const T *dout_ptr,
T *dx_ptr,
ScaleT *dscale_ptr,
ScaleT *dbias_ptr,
const MaskType *mask_ptr = nullptr,
T factor = static_cast<T>(0),
T *d_dropout_src_ptr = nullptr) {
auto stream = dev_ctx.stream();
if (cols == 1024 || cols == 384 || cols == 256) {
// step-1: compute dx and reduced part results of dscale and dbias.
const int WARPS_M = 4; // how many rows deal in a cta.
const int WARPS_N = 1; // how many warps to deal with a row.
const int BYTES_PER_LDG = 16;
const int VecSize = BYTES_PER_LDG / sizeof(T);
const int THREADS_PER_WARP = 32;
const int THREADS_PER_ROW = WARPS_N * THREADS_PER_WARP;
const int THREADS_PER_CTA = WARPS_M * THREADS_PER_ROW;
const int ROWS_PER_CTA = WARPS_M;
// 4 * 1024 * 4
const int SMEM_BYTES = ROWS_PER_CTA * cols * sizeof(U);
// #blocks = 2 * #SM
const int gridx = 2 * dev_ctx.GetSMCount();
// get temp space for dscale and dbias.
DenseTensor dscale_temp;
dscale_temp.Resize({gridx, cols});
dev_ctx.template Alloc<U>(&dscale_temp);
U *dscale_temp_ptr = dscale_temp.data<U>();
DenseTensor dbias_temp;
dbias_temp.Resize({gridx, cols});
dev_ctx.template Alloc<U>(&dbias_temp);
U *dbias_temp_ptr = dbias_temp.data<U>();
if (mask_ptr != nullptr) {
if (d_dropout_src_ptr == nullptr) {
PADDLE_THROW(common::errors::InvalidArgument(
"To compute fused_dropout_residual_ln grad, d_dropout_src_ptr "
"can't be null"));
}
#define LAUNCH_MASK_FUSED_LN_BWD_FAST_KERNEL(vec_size, ele_per_row) \
fused_ln_bwd_fast_kernel<true, \
true, \
T, \
U, \
ScaleT, \
MaskType, \
vec_size, \
WARPS_M, \
WARPS_N, \
BYTES_PER_LDG, \
ele_per_row> \
<<<gridx, THREADS_PER_CTA, 0, stream>>>(rows, \
epsilon, \
x_ptr, \
scale_ptr, \
mean_ptr, \
var_ptr, \
dout_ptr, \
dscale_temp_ptr, \
dbias_temp_ptr, \
dx_ptr, \
mask_ptr, \
factor, \
d_dropout_src_ptr);
if (cols == 1024) {
LAUNCH_MASK_FUSED_LN_BWD_FAST_KERNEL(VecSize, 1024);
} else {
switch (cols) {
case 384:
LAUNCH_MASK_FUSED_LN_BWD_FAST_KERNEL(1, 384);
break;
case 256:
LAUNCH_MASK_FUSED_LN_BWD_FAST_KERNEL(VecSize, 256);
break;
}
}
#undef LAUNCH_MASK_FUSED_LN_BWD_FAST_KERNEL
} else {
#define LAUNCH_FUSED_LN_BWD_FAST_KERNEL_BASE( \
vec_size, ele_per_row, need_d_dropout_src_ptr) \
fused_ln_bwd_fast_kernel<false, \
need_d_dropout_src_ptr, \
T, \
U, \
ScaleT, \
MaskType, \
vec_size, \
WARPS_M, \
WARPS_N, \
BYTES_PER_LDG, \
ele_per_row> \
<<<gridx, THREADS_PER_CTA, 0, stream>>>(rows, \
epsilon, \
x_ptr, \
scale_ptr, \
mean_ptr, \
var_ptr, \
dout_ptr, \
dscale_temp_ptr, \
dbias_temp_ptr, \
dx_ptr, \
nullptr, \
factor, \
d_dropout_src_ptr);
#define LAUNCH_FUSED_LN_BWD_FAST_KERNEL(vec_size, ele_per_row) \
do { \
if (d_dropout_src_ptr != nullptr) { \
LAUNCH_FUSED_LN_BWD_FAST_KERNEL_BASE(vec_size, ele_per_row, true); \
} else { \
LAUNCH_FUSED_LN_BWD_FAST_KERNEL_BASE(vec_size, ele_per_row, false); \
} \
} while (0)
if (cols == 1024) {
LAUNCH_FUSED_LN_BWD_FAST_KERNEL(VecSize, 1024);
} else {
switch (cols) {
case 384:
LAUNCH_FUSED_LN_BWD_FAST_KERNEL(1, 384);
break;
case 256:
LAUNCH_FUSED_LN_BWD_FAST_KERNEL(VecSize, 256);
break;
}
}
#undef LAUNCH_FUSED_LN_BWD_FAST_KERNEL
}
const int WARPS_M_2 = 16;
const int WARPS_N_2 = 1;
const int BYTES_PER_LDG_2 = 4;
const int VecSize_2 =
std::max(1, static_cast<int>(BYTES_PER_LDG_2 / sizeof(U))); // 1
const int THREADS_PER_WARP_2 = 32;
const int THREADS_PER_ROW_2 = WARPS_N_2 * THREADS_PER_WARP_2; // 32
const int THREADS_PER_CTA_2 =
WARPS_M_2 * THREADS_PER_ROW_2; // 16 * 32 = 512
const int ROWS_PER_CTA_2 = WARPS_M_2; // 16
// #blocks: 32, #threads_per_block: 512
// Note: it is not supported for double type.
if (sizeof(U) > 4) {
PADDLE_THROW(
common::errors::InvalidArgument("Only support float and fp16 type"));
} else {
int gridx_2 = 0;
#define LAUNCH_LN_BWD_BETA_GAMMMA_KERNEL(vec_size, ele_per_row) \
gridx_2 = static_cast<int>(std::ceil( \
ele_per_row / static_cast<float>(THREADS_PER_ROW_2 * vec_size))); \
ln_bwd_fast_final_kernel<U, \
ScaleT, \
vec_size, \
WARPS_M_2, \
WARPS_N_2, \
BYTES_PER_LDG_2, \
ele_per_row> \
<<<gridx_2, THREADS_PER_CTA_2, 0, stream>>>( \
gridx, dscale_temp_ptr, dbias_temp_ptr, dscale_ptr, dbias_ptr);
if (cols == 1024) {
LAUNCH_LN_BWD_BETA_GAMMMA_KERNEL(VecSize_2, 1024);
} else {
switch (cols) {
case 384:
LAUNCH_LN_BWD_BETA_GAMMMA_KERNEL(1, 384);
break;
case 256:
LAUNCH_LN_BWD_BETA_GAMMMA_KERNEL(VecSize_2, 256);
break;
}
}
#undef LAUNCH_LN_BWD_BETA_GAMMMA_KERNEL
}
} else {
PADDLE_THROW(common::errors::InvalidArgument(
"Fast layer_norm kernel is only used when feature_size is 1024"));
}
}
#endif
template <typename T, typename U, int BDIMX, int BDIMY, int VPTX>
__global__ void LayerNormBackwardPartGradGammaBeta(const T *__restrict__ dout,
const T *__restrict__ input,
const int64_t n1,
const int64_t n2,
const U *__restrict__ mean,
const U *__restrict__ var,
float epsilon,
U *part_grad_gamma,
U *part_grad_beta) {
// VPTX -> value per thread.x, BDIMX -> blockDim.x,
// BDIMY -> blockDim.y, template for compile time optimizations.
constexpr int RowStride = BDIMX + 1;
constexpr int BLOCK_SIZE = BDIMX * BDIMY;
constexpr int VPTX_MUL_BDIMY = VPTX * BDIMY;
constexpr int SharedSize = (BLOCK_SIZE > 2 * VPTX_MUL_BDIMY * RowStride)
? BLOCK_SIZE
: 2 * VPTX_MUL_BDIMY * RowStride;
const int thr_load_col_off = (threadIdx.x * VPTX) & (BDIMX - 1);
const int thr_load_row_off =
(threadIdx.x * VPTX) / BDIMX + threadIdx.y * BDIMY;
const int i2_off = blockIdx.x * BDIMX + thr_load_col_off;
__shared__ U buf[SharedSize];
U *warp_buf1 = reinterpret_cast<U *>(buf);
U *warp_buf2 = warp_buf1 + VPTX_MUL_BDIMY * RowStride;
for (int idx = threadIdx.y * BDIMX + threadIdx.x;
idx < 2 * VPTX_MUL_BDIMY * RowStride;
idx += BLOCK_SIZE) {
buf[idx] = U(0);
}
__syncthreads();
for (int64_t i1_block = static_cast<int64_t>(blockIdx.y) * BDIMY * VPTX;
i1_block < n1;
i1_block += VPTX_MUL_BDIMY * gridDim.y) {
cuLoadAddStridedInputs<T, U, VPTX>(i1_block,
thr_load_row_off,
thr_load_col_off,
i2_off,
RowStride,
warp_buf1,
warp_buf2,
input,
dout,
n1,
n2,
mean,
var,
epsilon);
}
__syncthreads();
// inter-warp reductions, sum within each warp
U acc1 = U(0);
U acc2 = U(0);
#pragma unroll
for (int k = 0; k < VPTX; ++k) {
int row1 = threadIdx.y + k * VPTX;
int idx1 = row1 * RowStride + threadIdx.x;
acc1 += warp_buf1[idx1];
acc2 += warp_buf2[idx1];
}
warp_buf1[threadIdx.y * RowStride + threadIdx.x] = acc1;
warp_buf2[threadIdx.y * RowStride + threadIdx.x] = acc2;
__syncthreads();
// sum all warps
#pragma unroll
for (int offset = VPTX >> 1; offset > 1; offset >>= 1) {
if (threadIdx.y < offset) {
int row1 = threadIdx.y;
int row2 = threadIdx.y + offset;
int idx1 = row1 * RowStride + threadIdx.x;
int idx2 = row2 * RowStride + threadIdx.x;
warp_buf1[idx1] += warp_buf1[idx2];
warp_buf2[idx1] += warp_buf2[idx2];
}
__syncthreads();
}
int64_t i2 = static_cast<int64_t>(blockIdx.x) * BDIMX + threadIdx.x;
if (threadIdx.y == 0 && i2 < n2) {
int row1 = threadIdx.y;
int row2 = threadIdx.y + 1;
int idx1 = row1 * RowStride + threadIdx.x;
int idx2 = row2 * RowStride + threadIdx.x;
part_grad_beta[blockIdx.y * n2 + i2] = warp_buf1[idx1] + warp_buf1[idx2];
part_grad_gamma[blockIdx.y * n2 + i2] = warp_buf2[idx1] + warp_buf2[idx2];
}
}
template <typename T, typename U, int BDIMX, int BDIMY, typename ScaleT>
__global__ void LayerNormBackwardSumGradGammaBeta(const U *part_grad_gamma,
const U *part_grad_beta,
const int part_size,
const int64_t n1,
const int64_t n2,
ScaleT *grad_gamma,
ScaleT *grad_beta) {
// sum partial gradients for gamma and beta
__shared__ U buf[BDIMX * BDIMY];
int64_t i2 = static_cast<int64_t>(blockIdx.x) * BDIMX + threadIdx.x;
if (i2 < n2) {
// each warp does sequential reductions until reduced part_size is num_warps
int num_warp_reductions = part_size / BDIMY;
U sum_gamma = U(0);
U sum_beta = U(0);
const U *part_grad_gamma_ptr =
part_grad_gamma + threadIdx.y * num_warp_reductions * n2 + i2;
const U *part_grad_beta_ptr =
part_grad_beta + threadIdx.y * num_warp_reductions * n2 + i2;
for (int warp_offset = 0; warp_offset < num_warp_reductions;
++warp_offset) {
sum_gamma += part_grad_gamma_ptr[warp_offset * n2];
sum_beta += part_grad_beta_ptr[warp_offset * n2];
}
// inter-warp reductions
constexpr int nbsize3 = BDIMX * BDIMY / 2;
for (int offset = BDIMY / 2; offset >= 1; offset /= 2) {
// top half write to shared memory
if (threadIdx.y >= offset && threadIdx.y < 2 * offset) {
const int write_idx = (threadIdx.y - offset) * blockDim.x + threadIdx.x;
buf[write_idx] = sum_gamma;
buf[write_idx + nbsize3] = sum_beta;
}
__syncthreads();
// bottom half sums
if (threadIdx.y < offset) {
const int read_idx = threadIdx.y * BDIMX + threadIdx.x;
sum_gamma += buf[read_idx];
sum_beta += buf[read_idx + nbsize3];
}
__syncthreads();
}
// write out fully summed gradients
if (threadIdx.y == 0) {
grad_gamma[i2] = static_cast<ScaleT>(sum_gamma);
grad_beta[i2] = static_cast<ScaleT>(sum_beta);
}
}
}
template <typename T, typename U, int BDIMX, int BDIMY, typename ScaleT>
__global__ void LayerNormBackwardComputeGradInput(const T *__restrict__ dout,
const T *__restrict__ input,
const int64_t n1,
const int64_t n2,
const U *__restrict__ mean,
const U *__restrict__ var,
const float epsilon,
const ScaleT *gamma,
T *grad_input) {
#ifdef __HIPCC__
for (int64_t i1 = hipBlockIdx_x; i1 < n1; i1 += hipGridDim_x) {
#else
for (int64_t i1 = blockIdx.x; i1 < n1; i1 += gridDim.x) {
#endif
U sum_loss1 = U(0);
U sum_loss2 = U(0);
const U c_mean = mean[i1];
const U c_invvar = rsqrt_<U>(var[i1] + epsilon);
const T *k_input = input + i1 * n2;
const T *k_dout = dout + i1 * n2;
constexpr int numx = BDIMX * BDIMY;
const int thrx = threadIdx.x + threadIdx.y * BDIMX;
if (gamma != NULL) {
int64_t l = 4 * thrx;
for (; l + 3 < n2; l += 4 * numx) {
for (int k = 0; k < 4; ++k) {
const U c_h = static_cast<U>(k_input[l + k]);
const U c_loss = static_cast<U>(k_dout[l + k]);
sum_loss1 += c_loss * static_cast<U>(gamma[l + k]);
sum_loss2 +=
c_loss * static_cast<U>(gamma[l + k]) * (c_h - c_mean) * c_invvar;
}
}
for (; l < n2; ++l) {
const U c_h = static_cast<U>(k_input[l]);
const U c_loss = static_cast<U>(k_dout[l]);
sum_loss1 += c_loss * static_cast<U>(gamma[l]);
sum_loss2 +=
c_loss * static_cast<U>(gamma[l]) * (c_h - c_mean) * c_invvar;
}
} else {
int64_t l = 4 * thrx;
for (; l + 3 < n2; l += 4 * numx) {
for (int k = 0; k < 4; ++k) {
const U c_h = static_cast<U>(k_input[l + k]);
const U c_loss = static_cast<U>(k_dout[l + k]);
sum_loss1 += c_loss;
sum_loss2 += c_loss * (c_h - c_mean) * c_invvar;
}
}
for (; l < n2; ++l) {
const U c_h = static_cast<U>(k_input[l]);
const U c_loss = static_cast<U>(k_dout[l]);
sum_loss1 += c_loss;
sum_loss2 += c_loss * (c_h - c_mean) * c_invvar;
}
}
// intra-warp reductions
#pragma unroll
for (int mask = BDIMX / 2; mask > 0; mask /= 2) {
#ifdef PADDLE_WITH_HIP
// WARP_SHFL_XOR(sum_loss, mask);
sum_loss1 += __shfl_xor(sum_loss1, mask, warpSize);
sum_loss2 += __shfl_xor(sum_loss2, mask, warpSize);
#else
// WARP_SHFL_XOR(sum_loss, mask);
sum_loss1 += __shfl_xor_sync(0xffffffff, sum_loss1, mask, warpSize);
sum_loss2 += __shfl_xor_sync(0xffffffff, sum_loss2, mask, warpSize);
#endif
}
// inter-warp reductions
if (BDIMY > 1) {
__shared__ U buf[BDIMX * BDIMY];
for (int offset = BDIMY / 2; offset > 0; offset /= 2) {
// upper half of warps write to shared
if (threadIdx.y >= offset && threadIdx.y < 2 * offset) {
const int wrt_i = (threadIdx.y - offset) * BDIMX + threadIdx.x;
buf[2 * wrt_i] = sum_loss1;
buf[2 * wrt_i + 1] = sum_loss2;
}
__syncthreads();
// lower half merges
if (threadIdx.y < offset) {
const int read_i = threadIdx.y * blockDim.x + threadIdx.x;
sum_loss1 += buf[2 * read_i];
sum_loss2 += buf[2 * read_i + 1];
}
__syncthreads();
}
if (threadIdx.y == 0) {
buf[2 * threadIdx.x] = sum_loss1;
buf[2 * threadIdx.x + 1] = sum_loss2;
}
__syncthreads();
if (threadIdx.y != 0) {
sum_loss1 = buf[2 * threadIdx.x];
sum_loss2 = buf[2 * threadIdx.x + 1];
}
}
// all threads now have the two sums over l
U fH = (U)n2;
U term1 = (U(1) / fH) * c_invvar;
T *k_grad_input = grad_input + i1 * n2;
if (gamma != NULL) {
for (int64_t l = thrx; l < n2; l += numx) {
const U c_h = static_cast<U>(k_input[l]);
const U c_loss = static_cast<U>(k_dout[l]);
U f_grad_input = fH * c_loss * static_cast<U>(gamma[l]);
f_grad_input -= sum_loss1;
f_grad_input -= (c_h - c_mean) * c_invvar * sum_loss2;
f_grad_input *= term1;
k_grad_input[l] = static_cast<T>(f_grad_input);
}
} else {
for (int64_t l = thrx; l < n2; l += numx) {
const U c_h = static_cast<U>(k_input[l]);
const U c_loss = static_cast<U>(k_dout[l]);
U f_grad_input = fH * c_loss;
f_grad_input -= sum_loss1;
f_grad_input -= (c_h - c_mean) * c_invvar * sum_loss2;
f_grad_input *= term1;
k_grad_input[l] = static_cast<T>(f_grad_input);
}
}
}
}
template <typename T, typename U, typename ScaleT, int DataPerTid>
__global__ void LayerNormBackwardComputeGradInputWithSmallFeatureSize(
const T *__restrict__ dout,
const T *__restrict__ input,
const int n1,
const int n2,
const U *__restrict__ mean,
const U *__restrict__ var,
const float epsilon,
const ScaleT *__restrict__ gamma,
T *grad_input) {
constexpr int WarpSize = 32;
#ifdef __HIPCC__
for (int64_t bid = hipBlockIdx_x; bid < n1; bid += hipGridDim_x) {
#else
for (int64_t bid = blockIdx.x; bid < n1; bid += gridDim.x) {
#endif
U sum_loss1 = U(0);
U sum_loss2 = U(0);
const U c_mean = mean[bid];
const U c_invvar = rsqrt_<U>(var[bid] + epsilon);
const int64_t main_vec_n2 = n2 / DataPerTid;
const int64_t tid_num = WarpSize * blockDim.y;
const int64_t thrx = threadIdx.x + threadIdx.y * WarpSize;
// One feature-size per block.
const T *__restrict__ k_dout = dout + bid * n2;
const T *__restrict__ k_input = input + bid * n2;
T *k_grad_input = grad_input + bid * n2;
// Data storage location in local register.
using VecT = AlignedVector<T, DataPerTid>;
using VecScaleT = AlignedVector<ScaleT, DataPerTid>;
const VecT *__restrict__ v_k_dout =
reinterpret_cast<const VecT *__restrict__>(k_dout);
const VecT *__restrict__ v_k_input =
reinterpret_cast<const VecT *__restrict__>(k_input);
const VecScaleT *__restrict__ v_gamma =
reinterpret_cast<const VecScaleT *__restrict__>(gamma);
VecT *v_grad = reinterpret_cast<VecT *>(k_grad_input);
// Each thread shall deal with no more than 8 data.
U dout_data[8];
U input_data[8];
U gamma_data[8];
if (gamma != NULL) {
int64_t tid = thrx;
for (int64_t i = 0; tid < main_vec_n2; tid += tid_num, ++i) {
VecT v_tmp_dout = v_k_dout[tid];
VecT v_tmp_input = v_k_input[tid];
VecScaleT v_tmp_gamma = v_gamma[tid];
#pragma unroll
for (int64_t k = 0; k < DataPerTid; ++k) {
const int64_t idx = k + i * DataPerTid;
dout_data[idx] = static_cast<U>(v_tmp_dout[k]);
input_data[idx] = static_cast<U>(v_tmp_input[k]);
gamma_data[idx] = static_cast<U>(v_tmp_gamma[k]);
sum_loss1 += dout_data[idx] * gamma_data[idx];
sum_loss2 += dout_data[idx] * gamma_data[idx] *
(input_data[idx] - c_mean) * c_invvar;
}
}
} else {
int64_t tid = thrx;
for (int64_t i = 0; tid < main_vec_n2; tid += tid_num, ++i) {
VecT v_tmp_dout = v_k_dout[tid];
VecT v_tmp_input = v_k_input[tid];
#pragma unroll
for (int64_t k = 0; k < DataPerTid; ++k) {
const int64_t idx = k + i * DataPerTid;
dout_data[idx] = static_cast<U>(v_tmp_dout[k]);
input_data[idx] = static_cast<U>(v_tmp_input[k]);
sum_loss1 += dout_data[idx];
sum_loss2 += dout_data[idx] * (input_data[idx] - c_mean) * c_invvar;
}
}
}
// intra-warp reductions
#pragma unroll
for (int mask = WarpSize / 2; mask > 0; mask /= 2) {
#ifdef PADDLE_WITH_HIP
// WARP_SHFL_XOR(sum_loss, mask);
sum_loss1 += __shfl_xor(sum_loss1, mask, warpSize);
sum_loss2 += __shfl_xor(sum_loss2, mask, warpSize);
#else
// WARP_SHFL_XOR(sum_loss, mask);
sum_loss1 += __shfl_xor_sync(0xffffffff, sum_loss1, mask, WarpSize);
sum_loss2 += __shfl_xor_sync(0xffffffff, sum_loss2, mask, WarpSize);
#endif
}
// inter-warp reductions
if (blockDim.y > 1) {
__shared__ U buf[512];
for (int offset = blockDim.y / 2; offset > 0; offset /= 2) {
// upper half of warps write to shared
if (threadIdx.y >= offset && threadIdx.y < 2 * offset) {
const int wrt_i = (threadIdx.y - offset) * WarpSize + threadIdx.x;
buf[2 * wrt_i] = sum_loss1;
buf[2 * wrt_i + 1] = sum_loss2;
}
__syncthreads();
// lower half merges
if (threadIdx.y < offset) {
const int read_i = threadIdx.y * blockDim.x + threadIdx.x;
sum_loss1 += buf[2 * read_i];
sum_loss2 += buf[2 * read_i + 1];
}
__syncthreads();
}
if (threadIdx.y == 0) {
buf[2 * threadIdx.x] = sum_loss1;
buf[2 * threadIdx.x + 1] = sum_loss2;
}
__syncthreads();
if (threadIdx.y != 0) {
sum_loss1 = buf[2 * threadIdx.x];
sum_loss2 = buf[2 * threadIdx.x + 1];
}
}
U fH = static_cast<U>(n2);
U ratio_term = (static_cast<U>(1) / fH) * c_invvar;
if (gamma != NULL) {
int64_t tid = thrx;
for (int64_t i = 0; tid < main_vec_n2; tid += tid_num, ++i) {
VecT temp_grad;
#pragma unroll
for (int k = 0; k < DataPerTid; ++k) {
const int idx = i * DataPerTid + k;
const U c_h = input_data[idx];
const U c_loss = dout_data[idx];
U f_grad_input = fH * c_loss * gamma_data[idx] - sum_loss1;
f_grad_input -= (c_h - c_mean) * c_invvar * sum_loss2;
temp_grad[k] = static_cast<T>(f_grad_input * ratio_term);
}
v_grad[tid] = temp_grad;
}
} else {
int64_t tid = thrx;
for (int64_t i = 0; tid < main_vec_n2; tid += tid_num, ++i) {
VecT temp_grad;
#pragma unroll
for (int k = 0; k < DataPerTid; ++k) {
const int idx = i * DataPerTid + k;
const U c_h = input_data[idx];
const U c_loss = dout_data[idx];
U f_grad_input = fH * c_loss - sum_loss1;
f_grad_input -= (c_h - c_mean) * c_invvar * sum_loss2;
temp_grad[k] = static_cast<T>(f_grad_input * ratio_term);
}
v_grad[tid] = temp_grad;
}
}
}
}
// Make sure that d_scale != nullptr && d_bias != nullptr
// Since d_scale != nullptr, scale would not be nullptr
template <typename T,
typename U,
int BlockDim,
bool HasDx,
bool ScaleBiasWithSameTypeX>
__global__ void LayerNormBackwardGradientAll(
const T *x,
const T *d_y,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *d_scale,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *d_bias,
T *d_x,
const U *mean,
const U *var,
const LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *scale,
float epsilon,
int64_t batch_size,
int64_t feature_size,
int64_t col_offset) {
using ScaleBiasT = LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX>;
int64_t beg_idx = static_cast<int64_t>(threadIdx.x) * feature_size +
(static_cast<int64_t>(blockIdx.x) + col_offset);
int64_t end_idx = batch_size * feature_size +
(static_cast<int64_t>(blockIdx.x) + col_offset);
int64_t stride = BlockDim * feature_size;
U d_scale_partial = static_cast<U>(0), d_bias_partial = static_cast<U>(0);
for (int64_t i = beg_idx; i < end_idx; i += stride) {
int row_idx = i / feature_size;
auto var_val = rsqrt_(static_cast<U>(var[row_idx]) + epsilon);
d_scale_partial += static_cast<U>(d_y[i]) *
(static_cast<U>(x[i]) - mean[row_idx]) * var_val;
d_bias_partial += static_cast<U>(d_y[i]);
if (HasDx) {
d_x[i] = static_cast<T>(static_cast<U>(d_y[i]) *
static_cast<U>(scale[blockIdx.x + col_offset]) *
var_val);
}
}
__shared__ U shared_scale[32]; // threadIdx.x / warpSize <= kMaxBlockDim /
// warpSize <= 1024/32 = 32;
__shared__ U shared_bias[32];
d_scale_partial = BlockReduceSum<U>(d_scale_partial, shared_scale);
d_bias_partial = BlockReduceSum<U>(d_bias_partial, shared_bias);
if (threadIdx.x == 0) {
d_scale[blockIdx.x + col_offset] = static_cast<ScaleBiasT>(d_scale_partial);
d_bias[blockIdx.x + col_offset] = static_cast<ScaleBiasT>(d_bias_partial);
}
}
// Make sure that there is only one true expression: d_scale != nullptr
// or d_bias != nullptr
// Notice: scale may be nullptr
template <typename T,
typename U,
int BlockDim,
bool HasDx,
bool HasDScale,
bool ScaleBiasWithSameTypeX>
__global__ void LayerNormBackwardGradientScaleOrBias(
const T *x,
const T *d_y,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *d_scale,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *d_bias,
T *d_x,
const U *mean,
const U *var,
const LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *scale,
float epsilon,
int64_t batch_size,
int64_t feature_size,
int col_offset) {
using ScaleBiasT = LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX>;
using BlockReduce = cub::BlockReduce<U, BlockDim>;
__shared__ typename BlockReduce::TempStorage temp_storage;
int64_t beg_idx = static_cast<int64_t>(threadIdx.x) * feature_size +
static_cast<int64_t>(blockIdx.x) + col_offset;
int64_t end_idx =
batch_size * feature_size + static_cast<int64_t>(blockIdx.x) + col_offset;
int64_t stride = BlockDim * feature_size;
U d_scale_or_d_bias_partial = static_cast<U>(0);
for (int64_t i = beg_idx; i < end_idx; i += stride) {
int row_idx = i / feature_size;
auto var_val =
static_cast<U>(rsqrt_(static_cast<float>(var[row_idx]) + epsilon));
if (HasDScale) {
d_scale_or_d_bias_partial += static_cast<U>(d_y[i]) *
(static_cast<U>(x[i]) - mean[row_idx]) *
var_val;
} else { // d_bias != nullptr
d_scale_or_d_bias_partial += static_cast<U>(d_y[i]);
}
if (HasDx) {
if (scale != nullptr) {
d_x[i] = static_cast<T>(static_cast<U>(d_y[i]) *
static_cast<U>(scale[blockIdx.x + col_offset]) *
var_val);
} else {
d_x[i] = static_cast<T>(static_cast<U>(d_y[i]) * var_val);
}
}
}
d_scale_or_d_bias_partial =
BlockReduce(temp_storage).Reduce(d_scale_or_d_bias_partial, cub::Sum());
if (threadIdx.x == 0) {
if (HasDScale) {
d_scale[blockIdx.x + col_offset] =
static_cast<ScaleBiasT>(d_scale_or_d_bias_partial);
} else {
d_bias[blockIdx.x + col_offset] =
static_cast<ScaleBiasT>(d_scale_or_d_bias_partial);
}
}
}
template <typename T, typename U, int BlockDim>
__global__ void LayerNormBackwardPostProcessToCalculateDX(
const T *x,
T *d_x,
const U *mean,
const U *var,
float epsilon,
int64_t feature_size) {
using BlockReduce = cub::BlockReduce<PairForLayerNorm<U>, BlockDim>;
__shared__ typename BlockReduce::TempStorage temp_storage;
__shared__ U d_x_reduce_tmp[2];
int64_t beg_idx = static_cast<int64_t>(blockIdx.x) * feature_size +
static_cast<int64_t>(threadIdx.x);
int64_t end_idx = (static_cast<int64_t>(blockIdx.x) + 1) * feature_size;
U block_mean = mean[blockIdx.x];
U block_var = var[blockIdx.x];
U d_x_mean_partial = static_cast<U>(0), d_x_var_partial = static_cast<U>(0);
for (int64_t i = beg_idx; i < end_idx; i += BlockDim) {
d_x_mean_partial += static_cast<U>(d_x[i]);
d_x_var_partial +=
static_cast<U>(d_x[i]) * (static_cast<U>(x[i]) - block_mean);
}
auto pair =
BlockReduce(temp_storage)
.Reduce(PairForLayerNorm<U>(d_x_mean_partial, d_x_var_partial),
PairForLayerNormAddFunctor<U>());
if (threadIdx.x == 0) {
d_x_reduce_tmp[0] = static_cast<float>(pair.first_) / feature_size;
d_x_reduce_tmp[1] =
static_cast<float>(pair.second_) /
(feature_size * (static_cast<float>(block_var) + epsilon));
}
__syncthreads();
d_x_mean_partial = d_x_reduce_tmp[0];
d_x_var_partial = d_x_reduce_tmp[1];
for (int64_t i = beg_idx; i < end_idx; i += BlockDim) {
d_x[i] -= static_cast<T>(d_x_mean_partial);
d_x[i] -=
static_cast<T>((static_cast<U>(x[i]) - block_mean) * d_x_var_partial);
}
}
// Here, we only calculate d_x
template <typename T, typename U, int BlockDim, bool ScaleBiasWithSameTypeX>
__global__ void LayerNormBackwardGradientOnlyDX(
const T *x,
const T *d_y,
T *d_x,
const U *mean,
const U *var,
const LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *scale,
float epsilon,
int64_t feature_size) {
using ScaleBiasT = LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX>;
using BlockReduce = cub::BlockReduce<PairForLayerNorm<U>, BlockDim>;
__shared__ typename BlockReduce::TempStorage temp_storage;
__shared__ U d_x_reduce_tmp[2];
int64_t beg_idx = static_cast<int64_t>(blockIdx.x) * feature_size +
static_cast<int64_t>(threadIdx.x);
int64_t end_idx = (static_cast<int64_t>(blockIdx.x) + 1) * feature_size;
U block_mean = mean[blockIdx.x], block_var = var[blockIdx.x];
U d_x_mean_partial = static_cast<U>(0), d_x_var_partial = static_cast<U>(0);
for (int64_t i = beg_idx; i < end_idx; i += BlockDim) {
auto var_val =
static_cast<U>(rsqrt_(static_cast<float>(block_var) + epsilon));
if (scale != nullptr) {
int col_idx = i % feature_size;
d_x[i] = static_cast<T>(static_cast<U>(d_y[i]) *
static_cast<U>(scale[col_idx]) * var_val);
} else {
d_x[i] = static_cast<T>(static_cast<U>(d_y[i]) * var_val);
}
d_x_mean_partial += static_cast<U>(d_x[i]);
d_x_var_partial +=
static_cast<U>(d_x[i]) * (static_cast<U>(x[i]) - block_mean);
}
auto pair =
BlockReduce(temp_storage)
.Reduce(PairForLayerNorm<U>(d_x_mean_partial, d_x_var_partial),
PairForLayerNormAddFunctor<U>());
if (threadIdx.x == 0) {
d_x_reduce_tmp[0] = static_cast<float>(pair.first_) / feature_size;
d_x_reduce_tmp[1] =
static_cast<float>(pair.second_) /
(feature_size * (static_cast<float>(block_var) + epsilon));
}
__syncthreads();
d_x_mean_partial = d_x_reduce_tmp[0];
d_x_var_partial = d_x_reduce_tmp[1];
for (int64_t i = beg_idx; i < end_idx; i += BlockDim) {
d_x[i] -= static_cast<T>(d_x_mean_partial);
d_x[i] -=
static_cast<T>((static_cast<U>(x[i]) - block_mean) * d_x_var_partial);
}
}
template <typename T, typename U, bool ScaleBiasWithSameTypeX>
__global__ void LayerNormBackwardWhenBatchSizeIsOne(
const T *x,
const T *d_y,
T *d_x,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *d_scale,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *d_bias,
const U *mean,
const U *var,
const LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *scale,
float epsilon,
int64_t feature_size) {
int64_t idx =
static_cast<int64_t>(threadIdx.x) +
static_cast<int64_t>(blockIdx.x) * static_cast<int64_t>(blockDim.x);
using ScaleBiasT = LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX>;
if (idx < feature_size) {
auto var_val = static_cast<U>(rsqrt_(static_cast<float>(var[0]) + epsilon));
if (d_x != nullptr) {
if (d_scale == nullptr) {
d_x[idx] = static_cast<T>(static_cast<U>(d_y[idx]) * var_val);
} else {
d_x[idx] = static_cast<T>(static_cast<U>(d_y[idx]) *
static_cast<U>(scale[idx]) * var_val);
}
}
if (d_scale != nullptr) {
d_scale[idx] =
static_cast<ScaleBiasT>(static_cast<U>(d_y[idx]) *
(static_cast<U>(x[idx]) - mean[0]) * var_val);
}
if (d_bias != nullptr) {
d_bias[idx] = static_cast<ScaleBiasT>(d_y[idx]);
}
}
}
inline int VecSizeJudgeForeGradInput(const int feature_size,
const int vec_size) {
if (!(feature_size & (vec_size - 1))) {
return vec_size;
} else if (vec_size == 4) {
if (!(feature_size & 1)) {
return 2;
}
}
return 1;
}
template <typename T, typename U, bool ScaleBiasWithSameTypeX = false>
static void LayerNormBackward(
const T *x,
const T *d_y,
const LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *scale,
const U *mean,
const U *var,
T *d_x,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *d_scale,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX> *d_bias,
float epsilon,
int64_t batch_size,
int64_t feature_size,
const GPUContext &dev_ctx) {
auto stream = dev_ctx.stream();
const int kMaxBlockDim = 512;
const int kMaxBlockNum = 128;
// TODO(large-tensor): generic backward kernel launch uses int32 grid dim
PADDLE_ENFORCE_LE_INT_MAX(batch_size, "batch_size");
int gradient_flag = ((d_x != nullptr ? 1 : 0) << 2) |
((d_scale != nullptr ? 1 : 0) << 1) |
((d_bias != nullptr ? 1 : 0));
if (gradient_flag == 0) return;
if (batch_size == 1) {
// TODO(large-tensor): batch_size==1 path uses int32 grid dim
PADDLE_ENFORCE_LE_INT_MAX(
(feature_size + kMaxBlockDim - 1) / kMaxBlockDim,
"(feature_size + kMaxBlockDim - 1) / kMaxBlockDim");
LayerNormBackwardWhenBatchSizeIsOne<T, U, ScaleBiasWithSameTypeX>
<<<(feature_size + kMaxBlockDim - 1) / kMaxBlockDim,
kMaxBlockDim,
0,
stream>>>(x,
d_y,
d_x,
d_scale,
d_bias,
mean,
var,
scale,
epsilon,
feature_size);
if (d_x != nullptr) {
switch (GetDesiredBlockDim(feature_size)) {
FIXED_BLOCK_DIM_CASE(
LayerNormBackwardPostProcessToCalculateDX<T, U, kBlockDim>
<<<1, kBlockDim, 0, stream>>>(
x, d_x, mean, var, epsilon, feature_size));
}
}
return;
}
auto block_dim = GetDesiredBlockDim(batch_size);
switch (gradient_flag) {
case 1: // d_x == nulptr, d_scale == nullptr, d_bias != nullptr
switch (block_dim) {
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE(
feature_size,
kMaxBlockNum,
LayerNormBackwardGradientScaleOrBias<T,
U,
kBlockDim,
false,
false,
ScaleBiasWithSameTypeX>
<<<block_num, kBlockDim, 0, stream>>>(x,
d_y,
d_scale,
d_bias,
d_x,
mean,
var,
scale,
epsilon,
batch_size,
feature_size,
col_offset));
}
break;
case 2: // d_x == nullptr, d_scale != nullptr, d_bias == nullptr
switch (block_dim) {
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE(
feature_size,
kMaxBlockNum,
LayerNormBackwardGradientScaleOrBias<T,
U,
kBlockDim,
false,
true,
ScaleBiasWithSameTypeX>
<<<block_num, kBlockDim, 0, stream>>>(x,
d_y,
d_scale,
d_bias,
d_x,
mean,
var,
scale,
epsilon,
batch_size,
feature_size,
col_offset));
}
break;
case 3: // d_x == nullptr, d_scale != nulptr, d_bias != nullptr
switch (block_dim) {
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE(
feature_size,
kMaxBlockNum,
LayerNormBackwardGradientAll<T,
U,
kBlockDim,
false,
ScaleBiasWithSameTypeX>
<<<block_num, kBlockDim, 0, stream>>>(x,
d_y,
d_scale,
d_bias,
d_x,
mean,
var,
scale,
epsilon,
batch_size,
feature_size,
col_offset));
}
break;
case 4: // d_x != nullptr, d_scale == nullptr, d_bias == nullptr
switch (GetDesiredBlockDim(feature_size)) {
FIXED_BLOCK_DIM_CASE(
LayerNormBackwardGradientOnlyDX<T,
U,
kBlockDim,
ScaleBiasWithSameTypeX>
<<<batch_size, kBlockDim, 0, stream>>>(
x, d_y, d_x, mean, var, scale, epsilon, feature_size));
}
break;
case 5: // d_x != nulptr, d_scale == nullptr, d_bias != nullptr
switch (block_dim) {
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE(
feature_size,
kMaxBlockNum,
LayerNormBackwardGradientScaleOrBias<T,
U,
kBlockDim,
true,
false,
ScaleBiasWithSameTypeX>
<<<block_num, kBlockDim, 0, stream>>>(x,
d_y,
d_scale,
d_bias,
d_x,
mean,
var,
scale,
epsilon,
batch_size,
feature_size,
col_offset));
}
switch (GetDesiredBlockDim(feature_size)) {
FIXED_BLOCK_DIM_CASE(
LayerNormBackwardPostProcessToCalculateDX<T, U, kBlockDim>
<<<batch_size, kBlockDim, 0, stream>>>(
x, d_x, mean, var, epsilon, feature_size));
}
break;
case 6: // d_x != nullptr, d_scale != nullptr, d_bias == nullptr
switch (block_dim) {
FIXED_BLOCK_DIM_FIXED_BLOCK_NUM_CASE(
feature_size,
kMaxBlockNum,
LayerNormBackwardGradientScaleOrBias<T,
U,
kBlockDim,
true,
true,
ScaleBiasWithSameTypeX>
<<<block_num, kBlockDim, 0, stream>>>(x,
d_y,
d_scale,
d_bias,
d_x,
mean,
var,
scale,
epsilon,
batch_size,
feature_size,
col_offset));
}
switch (GetDesiredBlockDim(feature_size)) {
FIXED_BLOCK_DIM_CASE(
LayerNormBackwardPostProcessToCalculateDX<T, U, kBlockDim>
<<<batch_size, kBlockDim, 0, stream>>>(
x, d_x, mean, var, epsilon, feature_size));
}
break;
case 7: // d_x != nullptr, d_scale != nullptr, d_bias != nullptr
{
#ifdef PADDLE_WITH_CUDA
bool can_call_fast_kernel = false;
// todo: rule out double type.
if ((feature_size == 1024 || feature_size == 384 ||
feature_size == 256) &&
sizeof(T) <= 4) {
can_call_fast_kernel = true;
}
VLOG(6) << "can_call_fast_kernel = " << can_call_fast_kernel;
if (can_call_fast_kernel) {
ln_bwd_fast_kernel_driver<
T,
U,
LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX>>(dev_ctx,
batch_size,
feature_size,
epsilon,
x,
scale,
mean,
var,
d_y,
d_x,
d_scale,
d_bias);
} else {
#endif
using ScaleT = LayerNormScaleBiasT<T, U, ScaleBiasWithSameTypeX>;
constexpr int BDIMX = 32;
constexpr int VPT = 4;
constexpr int BDIMY1 = 4;
constexpr int PartSize = BDIMY1 * VPT;
dim3 threads2(BDIMX, BDIMY1, 1);
dim3 blocks2((feature_size + BDIMX - 1) / BDIMX, PartSize, 1);
int64_t param_num = PartSize * feature_size;
auto part_grad_param_ptr = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
param_num * sizeof(U) * 2, // for both gamma and beta
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
U *part_grad_gamma = reinterpret_cast<U *>(part_grad_param_ptr->ptr());
U *part_grad_beta = reinterpret_cast<U *>(part_grad_gamma + param_num);
LayerNormBackwardPartGradGammaBeta<T, U, BDIMX, BDIMY1, VPT>
<<<blocks2, threads2, 0, stream>>>(d_y,
x,
batch_size,
feature_size,
mean,
var,
epsilon,
part_grad_gamma,
part_grad_beta);
constexpr int BDIMY2 = 8;
dim3 threads3(BDIMX, BDIMY2, 1);
const dim3 blocks3((feature_size + BDIMX - 1) / BDIMX, 1, 1);
LayerNormBackwardSumGradGammaBeta<T, U, BDIMX, BDIMY2, ScaleT>
<<<blocks3, threads3, 0, stream>>>(part_grad_gamma,
part_grad_beta,
PartSize,
batch_size,
feature_size,
d_scale,
d_bias);
uint64_t addr = reinterpret_cast<uint64_t>(d_y) |
reinterpret_cast<uint64_t>(x) |
reinterpret_cast<uint64_t>(d_x);
int vec_size =
std::min(4, phi::GetVectorizedSize<T>(reinterpret_cast<T *>(addr)));
int real_vec = VecSizeJudgeForeGradInput(feature_size, vec_size);
if (feature_size <= 2048) {
// One thread must work with at least real_vec quantity data, at most
// 8 data.
int data_per_warp = BDIMX * real_vec;
uint32_t warp_num =
feature_size < data_per_warp ? 1 : (feature_size / data_per_warp);
#if defined(__clang__) || defined(__GNUC__)
int block_dim_y = std::min(8, 1 << (31 - __builtin_clz(warp_num)));
#else
int block_dim_y = 1;
while (warp_num != 0) {
warp_num = warp_num >> 1;
block_dim_y <<= 1;
}
block_dim_y = std::min(8, (block_dim_y / 2));
#endif // __GNUCC__
dim3 threads1(BDIMX, block_dim_y, 1);
#define IMPL_BACKWARD_FOR_INPUT(num) \
LayerNormBackwardComputeGradInputWithSmallFeatureSize<T, U, ScaleT, num> \
<<<batch_size, threads1, 0, stream>>>( \
d_y, x, batch_size, feature_size, mean, var, epsilon, scale, d_x);
switch (real_vec) {
case 4: {
IMPL_BACKWARD_FOR_INPUT(4);
} break;
case 2: {
IMPL_BACKWARD_FOR_INPUT(2);
} break;
default: {
IMPL_BACKWARD_FOR_INPUT(1);
}
}
#undef IMPL_BACKWARD_FOR_INPUT
} else {
constexpr int BDIMY3 = 4;
dim3 threads1(BDIMX, BDIMY3, 1);
LayerNormBackwardComputeGradInput<T, U, BDIMX, BDIMY3, ScaleT>
<<<batch_size, threads1, 0, stream>>>(d_y,
x,
batch_size,
feature_size,
mean,
var,
epsilon,
scale,
d_x);
}
#ifdef PADDLE_WITH_CUDA
}
#endif
break;
}
default:
break;
}
}
} // namespace funcs
} // namespace phi