// 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/interpolate_grad_kernel.h" #include "paddle/common/layout.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/backends/gpu/gpu_info.h" #include "paddle/phi/backends/gpu/gpu_launch_config.h" #include "paddle/phi/backends/gpu/gpu_primitives.h" #include "paddle/phi/common/amp_type_traits.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/funcs/interpolate_function.h" #include "paddle/phi/kernels/funcs/math_cuda_utils.h" #include "paddle/phi/kernels/funcs/math_function.h" #include "paddle/phi/kernels/gpu/interpolate.cuh" #include "paddle/phi/kernels/primitive/datamover_primitives.h" namespace phi { template __forceinline__ __device__ void PreCalculatorForLinearInterpInputIndex( int64_t* in_img_idx, int64_t* x_id, T* lambda1, T* lambda2, T src_x, const int64_t in_img_x) { src_x = max(src_x, T(0)); *in_img_idx = static_cast(src_x); *x_id = (*in_img_idx < in_img_x - 1) ? 1 : 0; *lambda1 = static_cast(src_x - *in_img_idx); *lambda2 = static_cast(1.0) - *lambda1; } template __global__ void KeLinearInterpBw(T* in, const size_t in_img_w, const size_t input_w, const T* out, const size_t out_img_w, const size_t output_h, const size_t output_w, const size_t num_channels, const MT ratio_w, const bool align_corners, const int align_mode, const DataLayout data_layout) { int64_t nthreads = output_h * output_w; int64_t tid = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; int64_t stride = static_cast(blockDim.x) * gridDim.x; bool align_flag = (align_mode == 0 && !align_corners); for (; tid < nthreads; tid += stride) { int64_t out_id_h = tid / output_w; int64_t out_id_w = tid % output_w; int64_t in_img_size = input_w / num_channels; int64_t out_img_size = output_w / num_channels; int64_t channel_id, out_img_idx; if (data_layout == DataLayout::NCHW) { channel_id = out_id_w / out_img_size; out_img_idx = tid % out_img_w; } else { out_img_idx = out_id_w % (out_img_w * num_channels) / num_channels; channel_id = tid % num_channels; } int64_t in_img_idx, w_id; MT w1lambda, w2lambda; MT src_w = funcs::AreaPixelComputeSourceIndex( ratio_w, out_img_idx, !align_flag); PreCalculatorForLinearInterpInputIndex( &in_img_idx, &w_id, &w1lambda, &w2lambda, src_w, in_img_w); T* in_pos; if (data_layout == DataLayout::NCHW) { in_pos = &in[out_id_h * input_w + channel_id * in_img_size + in_img_idx]; } else { in_pos = &in[out_id_h * input_w + in_img_idx * num_channels + channel_id]; } const T* out_pos; if (data_layout == DataLayout::NCHW) { out_pos = &out[out_id_h * output_w + channel_id * out_img_size + out_img_idx]; } else { out_pos = &out[out_id_h * output_w + out_img_idx * num_channels + channel_id]; } if (data_layout == DataLayout::NCHW) { CudaAtomicAdd(&in_pos[0], static_cast(w2lambda * static_cast(out_pos[0]))); CudaAtomicAdd(&in_pos[w_id], static_cast(w1lambda * static_cast(out_pos[0]))); } else { CudaAtomicAdd(&in_pos[0], static_cast(w2lambda * static_cast(out_pos[0]))); CudaAtomicAdd(&in_pos[w_id * num_channels], static_cast(w1lambda * static_cast(out_pos[0]))); } } } template __global__ void KeNearestNeighborInterpNCHWBw(T* in, const size_t in_img_h, const size_t in_img_w, const T* out, const size_t out_img_h, const size_t out_img_w, const size_t nc, const MT ratio_h, const MT ratio_w, const bool align_corners) { IndexType out_img_idx = static_cast(threadIdx.x) + static_cast(blockIdx.x) * static_cast(blockDim.x); IndexType out_img_idy = static_cast(threadIdx.y) + static_cast(blockIdx.y) * static_cast(blockDim.y); IndexType nc_id = static_cast(threadIdx.z) + static_cast(blockIdx.z) * static_cast(blockDim.z); IndexType nc_stride = static_cast(blockDim.z) * static_cast(gridDim.z); // nearest_sampling by multiple read in_addr and write to out_addr IndexType in_img_idx = (align_corners) ? static_cast(ratio_w * out_img_idx + 0.5) : static_cast(ratio_w * out_img_idx); IndexType in_img_idy = (align_corners) ? static_cast(ratio_h * out_img_idy + 0.5) : static_cast(ratio_h * out_img_idy); IndexType in_index = (nc_id * in_img_h + in_img_idy) * in_img_w + in_img_idx; IndexType in_index_stride = nc_stride * in_img_h * in_img_w; IndexType out_index = (nc_id * out_img_h + out_img_idy) * out_img_w + out_img_idx; IndexType out_index_stride = nc_stride * out_img_h * out_img_w; // prevent from multiple threads writing if (out_img_idx < out_img_w && out_img_idy < out_img_h) { while (nc_id < nc) { T* in_pos = &in[in_index]; const T out_pos = out[out_index]; CudaAtomicAdd(in_pos, out_pos); in_index += in_index_stride; out_index += out_index_stride; nc_id += nc_stride; } } } template __global__ void KeNearestNeighborInterpBw( T* in, const size_t in_img_h, const size_t in_img_w, const size_t input_h, const size_t input_w, const T* out, const size_t out_img_h, const size_t out_img_w, const size_t output_h, const size_t output_w, const size_t num_channels, const MT ratio_h, const MT ratio_w, const bool align_corners, funcs::FastDivModForInterpolate divmods) { int64_t nthreads = output_h * output_w; int64_t tid = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; int64_t stride = static_cast(blockDim.x) * gridDim.x; int64_t in_img_size = in_img_h * in_img_w; int64_t out_img_size = out_img_h * out_img_w; for (; tid < nthreads; tid += stride) { auto out_id_divmod = divmods.output_w_div.Divmod(tid); int64_t out_id_h = out_id_divmod.val[0]; int64_t out_id_w = out_id_divmod.val[1]; int64_t channel_id = divmods.channels_div.Divmod(tid).val[1]; auto outimg_id_divmod = divmods.output_wc_div.Divmod(out_id_w); int64_t out_img_idy = outimg_id_divmod.val[0]; int64_t out_img_idx = divmods.channels_div.Divmod(outimg_id_divmod.val[1]).val[0]; int64_t in_img_idy = (align_corners) ? static_cast(ratio_h * out_img_idy + 0.5) : static_cast(ratio_h * out_img_idy); int64_t in_img_idx = (align_corners) ? static_cast(ratio_w * out_img_idx + 0.5) : static_cast(ratio_w * out_img_idx); T* in_pos = &in[out_id_h * input_w + in_img_idy * in_img_w * num_channels + in_img_idx * num_channels + channel_id]; const T out_pos = out[tid]; CudaAtomicAdd(in_pos, out_pos); } } /* Calculate the minimum of partial elements in a block */ template __inline__ __device__ T PartialBlockMin(T val, size_t threads_num_in_block, unsigned mask) { __shared__ T shared[WARP_SIZE]; __shared__ T shared_last_val; __shared__ int shared_last_idx; int lane = threadIdx.x & 0x1f; int wid = threadIdx.x >> 5; int threshold = (threads_num_in_block & (-WARP_SIZE)); if (threadIdx.x < threshold) { shared_last_idx = (threshold >> 5) - 1; val = funcs::WarpReduceMin(val, mask); if (lane == 0) { shared[wid] = val; } } else { shared_last_val = std::numeric_limits::max(); CudaAtomicMin(&shared_last_val, val); shared[wid] = shared_last_val; shared_last_idx = wid; } __syncthreads(); if (threadIdx.x < threshold) { val = (lane <= shared_last_idx) ? shared[lane] : std::numeric_limits::max(); val = funcs::WarpReduceMin(val, mask); shared_last_val = val; } __syncthreads(); if (threadIdx.x >= threshold) { val = shared_last_val; } return val; } template __global__ void KeBilinearInterpBwShareMemory(T* in, const int64_t in_h, const int64_t in_w, const T* __restrict__ out, const int64_t out_h, const int64_t out_w, const int64_t n, const int64_t num_channels, MT ratio_h, MT ratio_w, const bool align_corners, const int align_mode, bool is_nchw) { __shared__ MT s_data[2][1024]; int64_t tid = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; int64_t stride = static_cast(blockDim.x) * gridDim.x; int64_t in_chw = in_h * in_w * num_channels; int64_t out_chw = num_channels * out_h * out_w; int64_t nthreads = static_cast(n) * out_chw; bool align_flag = (align_mode == 0 && !align_corners); for (; tid < nthreads; tid += stride) { int64_t out_id_h = tid / out_chw; int64_t out_id_w = tid % out_chw; const int64_t in_img_size = in_h * in_w; const int64_t out_img_size = out_h * out_w; MT value = static_cast(out[out_id_h * out_chw + out_id_w]); int64_t channel_id = out_id_w / out_img_size; int64_t out_img_idy = (out_id_w % out_img_size) / out_w; int64_t out_img_idx = tid % out_w; int64_t in_img_idx, in_img_idy, w_id, h_id; MT w1lambda, h1lambda, w2lambda, h2lambda; MT src_w = funcs::AreaPixelComputeSourceIndex( ratio_w, out_img_idx, !align_flag); MT src_h = funcs::AreaPixelComputeSourceIndex( ratio_h, out_img_idy, !align_flag); PreCalculatorForLinearInterpInputIndex( &in_img_idx, &w_id, &w1lambda, &w2lambda, src_w, in_w); PreCalculatorForLinearInterpInputIndex( &in_img_idy, &h_id, &h1lambda, &h2lambda, src_h, in_h); // top_left_index is just input_index. int64_t input_index = out_id_h * in_chw + channel_id * in_img_size + in_img_idy * in_w + in_img_idx; int64_t top_right_index = input_index + w_id; int64_t bot_left_index = input_index + h_id * in_w; int64_t bot_right_index = input_index + h_id * in_w + w_id; int64_t in_top_min_index, in_bot_min_index; s_data[0][threadIdx.x] = static_cast(0); s_data[1][threadIdx.x] = static_cast(0); int64_t remain = nthreads - (tid & (-static_cast(blockDim.x))); int64_t in_top_max_index = funcs::BlockReduceMax(top_right_index, FINAL_MASK); int64_t in_bot_max_index = funcs::BlockReduceMax(bot_right_index, FINAL_MASK); if (remain > blockDim.x) { in_top_min_index = funcs::BlockReduceMin(input_index, FINAL_MASK); in_bot_min_index = funcs::BlockReduceMin(bot_left_index, FINAL_MASK); } else { in_top_min_index = PartialBlockMin(input_index, remain, FINAL_MASK); in_bot_min_index = PartialBlockMin(bot_left_index, remain, FINAL_MASK); } int64_t upper_limit_share_idx = (in_top_max_index - in_top_min_index) > (in_bot_max_index - in_bot_min_index) ? (in_top_max_index - in_top_min_index) : (in_bot_max_index - in_bot_min_index); if (h_id != 0) { CudaAtomicAdd(&s_data[0][input_index - in_top_min_index], h2lambda * w2lambda * value); CudaAtomicAdd(&s_data[0][top_right_index - in_top_min_index], h2lambda * w1lambda * value); CudaAtomicAdd(&s_data[1][bot_left_index - in_bot_min_index], h1lambda * w2lambda * value); CudaAtomicAdd(&s_data[1][bot_right_index - in_bot_min_index], h1lambda * w1lambda * value); } else { CudaAtomicAdd(&s_data[0][top_right_index - in_top_min_index], (h2lambda + h1lambda) * w1lambda * value); CudaAtomicAdd(&s_data[1][bot_left_index - in_bot_min_index], (h1lambda + h2lambda) * w2lambda * value); } __syncthreads(); if (threadIdx.x <= upper_limit_share_idx) { CudaAtomicAdd(&in[in_top_min_index + threadIdx.x], static_cast(s_data[0][threadIdx.x])); CudaAtomicAdd(&in[in_bot_min_index + threadIdx.x], static_cast(s_data[1][threadIdx.x])); } } } __device__ __forceinline__ int64_t GetInputIndex(const int64_t nc, const int64_t height, const int64_t width, const int64_t h, const int64_t w) { return (nc * height + h) * width + w; } template __global__ void KeBilinearInterpNCHWBw(T* in, const int64_t in_h, const int64_t in_w, const int64_t out_h, const int64_t out_w, const int64_t n, const int64_t num_channels, MT ratio_h, MT ratio_w, const T* __restrict__ out, const bool align_corners, const int align_mode) { int64_t index = threadIdx.x + static_cast(blockDim.x) * blockIdx.x; const int64_t stride = static_cast(blockDim.x) * gridDim.x; const int64_t num_out = static_cast(n) * num_channels * out_h * out_w; const int64_t num_in = static_cast(n) * num_channels * in_h * in_w; MT align_type_value = (align_mode == 0 && !align_corners) ? 0.5 : 0; // Restricted parallelism if ratio_w is over threshold // to avoid atomic contention overhead. // This threshold 0.5f is come up with extensive quantitative analysis, // corresponding to 2x or larger scale factor in W axis. if (ratio_w < 0.5f) [[likely]] { // NOLINT if (index < num_in) { int64_t index_tmp = index; const int64_t w1 = index_tmp % in_w; index_tmp /= in_w; const int64_t h1 = index_tmp % in_h; const int64_t nc = index_tmp / in_h; MT d2val_sum = 0.0f; // Precompute constants const MT inv_ratio_h = 1.0f / ratio_h; const MT inv_ratio_w = 1.0f / ratio_w; // Compute the range of output pixels (h2_min, h2_max) that could affect // input pixel h1 const MT h2r_min = (h1 - 1 + align_type_value) * inv_ratio_h - align_type_value; const int64_t h2_min = max(static_cast(ceilf(h2r_min)), static_cast(0)); const MT h2r_max = (h1 + 1 + align_type_value) * inv_ratio_h - align_type_value; const int64_t h2_max = min(static_cast(floorf(h2r_max)), out_h - 1); // Compute the range of output pixels (w2_min, w2_max) that could affect // input pixel w1 const MT w2r_min = (w1 - 1 + align_type_value) * inv_ratio_w - align_type_value; const int64_t w2_min = max(static_cast(ceilf(w2r_min)), static_cast(0)); const MT w2r_max = (w1 + 1 + align_type_value) * inv_ratio_w - align_type_value; const int64_t w2_max = min(static_cast(floorf(w2r_max)), out_w - 1); for (int64_t h2 = h2_min; h2 <= h2_max; ++h2) { const MT src_y = ratio_h * (h2 + align_type_value) - align_type_value; int64_t h1_, y_id; MT h1lambda, h0lambda; PreCalculatorForLinearInterpInputIndex( &h1_, &y_id, &h1lambda, &h0lambda, src_y, in_h); if (h1 != h1_ && h1 != h1_ + y_id) [[unlikely]] { continue; } for (int64_t w2 = w2_min; w2 <= w2_max; ++w2) { int64_t w1_, x_id; const MT src_x = ratio_w * (w2 + align_type_value) - align_type_value; MT w1lambda, w0lambda; PreCalculatorForLinearInterpInputIndex( &w1_, &x_id, &w1lambda, &w0lambda, src_x, in_w); if (w1 != w1_ && w1 != w1_ + x_id) [[unlikely]] { continue; } const MT grad_output = out[nc * out_h * out_w + h2 * out_w + w2]; float hlambda = (h1 == h1_) ? h0lambda : 0.0f; hlambda += (h1 == h1_ + y_id) ? h1lambda : 0.0f; float wlambda = (w1 == w1_) ? w0lambda : 0.0f; wlambda += (w1 == w1_ + x_id) ? w1lambda : 0.0f; d2val_sum += hlambda * wlambda * grad_output; } } in[index] = static_cast(d2val_sum); } } else [[unlikely]] { // NOLINT for (; index < num_out; index += stride) { int64_t index_tmp = index; int64_t w2 = index_tmp % out_w; index_tmp /= out_w; int64_t h2 = index_tmp % out_h; int64_t nc = index_tmp / out_h; int64_t h1, y_id; MT h1lambda, h0lambda; MT src_y = static_cast(ratio_h * (h2 + align_type_value) - align_type_value); PreCalculatorForLinearInterpInputIndex( &h1, &y_id, &h1lambda, &h0lambda, src_y, in_h); int64_t w1, x_id; MT w1lambda, w0lambda; MT src_x = static_cast(ratio_w * (w2 + align_type_value) - align_type_value); PreCalculatorForLinearInterpInputIndex( &w1, &x_id, &w1lambda, &w0lambda, src_x, in_w); MT d2val = static_cast(out[index]); CudaAtomicAdd(in + GetInputIndex(nc, in_h, in_w, h1, w1), static_cast(h0lambda * w0lambda * d2val)); CudaAtomicAdd(in + GetInputIndex(nc, in_h, in_w, h1, w1 + x_id), static_cast(h0lambda * w1lambda * d2val)); CudaAtomicAdd(in + GetInputIndex(nc, in_h, in_w, h1 + y_id, w1), static_cast(h1lambda * w0lambda * d2val)); CudaAtomicAdd(in + GetInputIndex(nc, in_h, in_w, h1 + y_id, w1 + x_id), static_cast(h1lambda * w1lambda * d2val)); } } } template __global__ void KeBilinearInterpBw(T* in, const int64_t in_h, const int64_t in_w, const T* __restrict__ out, const int64_t out_h, const int64_t out_w, const int64_t n, const int64_t out_chw, const int64_t num_channels, MT ratio_h, MT ratio_w, const bool align_corners, const int align_mode, funcs::FastDivModForInterpolate divmods) { int64_t tid = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; int64_t stride = static_cast(blockDim.x) * gridDim.x; int64_t in_chw = in_h * in_w * num_channels; int64_t nthreads = static_cast(n) * out_chw; bool align_flag = (align_mode == 0 && !align_corners); for (; tid < nthreads; tid += stride) { auto out_id_divmod = divmods.output_w_div.Divmod(tid); int64_t out_id_h = out_id_divmod.val[0]; int64_t out_id_w = out_id_divmod.val[1]; int64_t channel_id = divmods.channels_div.Divmod(tid).val[1]; auto outimg_id_divmod = divmods.output_wc_div.Divmod(out_id_w); int64_t out_img_idy = outimg_id_divmod.val[0]; int64_t out_img_idx = divmods.channels_div.Divmod(outimg_id_divmod.val[1]).val[0]; int64_t in_img_idx, in_img_idy, w_id, h_id; MT w1lambda, h1lambda, w2lambda, h2lambda; MT src_w = funcs::AreaPixelComputeSourceIndex( ratio_w, out_img_idx, !align_flag); MT src_h = funcs::AreaPixelComputeSourceIndex( ratio_h, out_img_idy, !align_flag); PreCalculatorForLinearInterpInputIndex( &in_img_idx, &w_id, &w1lambda, &w2lambda, src_w, in_w); PreCalculatorForLinearInterpInputIndex( &in_img_idy, &h_id, &h1lambda, &h2lambda, src_h, in_h); MT value = static_cast(out[tid]); T* in_pos = &in[out_id_h * in_chw + in_img_idy * in_w * num_channels + in_img_idx * num_channels + channel_id]; CudaAtomicAdd(&in_pos[0], static_cast(h2lambda * w2lambda * value)); CudaAtomicAdd(&in_pos[w_id * num_channels], static_cast(h2lambda * w1lambda * value)); CudaAtomicAdd(&in_pos[h_id * in_w * num_channels], static_cast(h1lambda * w2lambda * value)); CudaAtomicAdd(&in_pos[h_id * in_w * num_channels + w_id * num_channels], static_cast(h1lambda * w1lambda * value)); } } template __global__ void KeBicubicInterpBw(T* in, const size_t in_img_h, const size_t in_img_w, const size_t input_h, const size_t input_w, const T* out, const size_t out_img_h, const size_t out_img_w, const size_t output_h, const size_t output_w, const size_t num_channels, const MT ratio_h, const MT ratio_w, const bool align_corners, const DataLayout data_layout) { int64_t nthreads = output_h * output_w; int64_t tid = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; int64_t stride = static_cast(blockDim.x) * gridDim.x; for (; tid < nthreads; tid += stride) { int64_t out_id_h = tid / output_w; int64_t out_id_w = tid % output_w; int64_t in_img_size = input_w / num_channels; int64_t out_img_size = output_w / num_channels; int64_t channel_id, out_img_idy, out_img_idx; if (data_layout == DataLayout::NCHW) { channel_id = out_id_w / out_img_size; out_img_idy = (out_id_w % out_img_size) / out_img_w; out_img_idx = tid % out_img_w; } else { out_img_idy = out_id_w / (out_img_w * num_channels); out_img_idx = out_id_w % (out_img_w * num_channels) / num_channels; channel_id = tid % num_channels; } MT in_img_idy = funcs::AreaPixelComputeSourceIndex( ratio_h, out_img_idy, align_corners); int64_t input_y = floorf(in_img_idy); const MT y_t = in_img_idy - input_y; MT in_img_idx = funcs::AreaPixelComputeSourceIndex( ratio_w, out_img_idx, align_corners); int64_t input_x = floorf(in_img_idx); const MT x_t = in_img_idx - input_x; MT x_coeffs[4]; MT y_coeffs[4]; funcs::GetCubicUpsampleCoefficients(x_coeffs, x_t); funcs::GetCubicUpsampleCoefficients(y_coeffs, y_t); const T* out_pos = &out[out_id_h * output_w + out_id_w]; T* in_pos; for (int64_t i = 0; i < 4; i++) { for (int64_t j = 0; j < 4; j++) { int64_t access_y = max(min(static_cast(input_y - 1 + j), static_cast(in_img_h - 1)), static_cast(0)); int64_t access_x = max(min(static_cast(input_x - 1 + i), static_cast(in_img_w - 1)), static_cast(0)); if (data_layout == DataLayout::NCHW) { in_pos = &in[out_id_h * input_w + channel_id * in_img_size + access_y * in_img_w + access_x]; } else { in_pos = &in[out_id_h * input_w + access_y * in_img_w * num_channels + access_x * num_channels + channel_id]; } CudaAtomicAdd(&in_pos[0], static_cast(static_cast(out_pos[0]) * y_coeffs[j] * x_coeffs[i])); } } } } template __global__ void KeTrilinearInterpBw(T* in, const size_t in_img_d, const size_t in_img_h, const size_t in_img_w, const size_t input_h, const size_t input_w, const T* out, const size_t out_img_d, const size_t out_img_h, const size_t out_img_w, const size_t output_h, const size_t output_w, const size_t num_channels, const MT ratio_d, const MT ratio_h, const MT ratio_w, const bool align_corners, const int align_mode, const DataLayout data_layout) { int64_t nthreads = output_h * output_w; int64_t tid = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; int64_t stride = static_cast(blockDim.x) * gridDim.x; bool align_flag = (align_mode == 0 && !align_corners); for (; tid < nthreads; tid += stride) { int64_t out_id_h = tid / output_w; int64_t out_id_w = tid % output_w; int64_t in_img_size = input_w / num_channels; int64_t out_img_size = output_w / num_channels; int64_t channel_id, out_img_idt, out_img_idy, out_img_idx; if (data_layout == DataLayout::NCHW) { channel_id = out_id_w / out_img_size; out_img_idt = (out_id_w % out_img_size) / out_img_h / out_img_w; out_img_idy = ((out_id_w % out_img_size) / out_img_w) % out_img_h; out_img_idx = tid % out_img_w; } else { out_img_idt = out_id_w / (out_img_h * out_img_w * num_channels); out_img_idy = out_id_w % (out_img_h * out_img_w * num_channels) / (out_img_w * num_channels); out_img_idx = out_id_w % (out_img_w * num_channels) / num_channels; channel_id = tid % num_channels; } int64_t in_img_idt = align_flag ? static_cast(ratio_d * (out_img_idt + 0.5) - 0.5) : static_cast(ratio_d * out_img_idt); in_img_idt = (in_img_idt > 0) ? in_img_idt : 0; int64_t d_id = (in_img_idt < in_img_d - 1) ? 1 : 0; double src_d = static_cast(ratio_d * (out_img_idt + 0.5) - 0.5); src_d = (src_d > static_cast(0)) ? src_d : static_cast(0); double d1lambda_mt = align_flag ? src_d - static_cast(in_img_idt) : static_cast(ratio_d) * static_cast(out_img_idt) - static_cast(in_img_idt); double d2lambda_mt = static_cast(1.0) - d1lambda_mt; int64_t in_img_idy = align_flag ? static_cast(ratio_h * (out_img_idy + 0.5) - 0.5) : static_cast(ratio_h * out_img_idy); in_img_idy = (in_img_idy > 0) ? in_img_idy : 0; int64_t h_id = (in_img_idy < in_img_h - 1) ? 1 : 0; double src_h = static_cast(ratio_h) * static_cast(out_img_idy + 0.5) - static_cast(0.5); src_h = (src_h > static_cast(0)) ? src_h : static_cast(0); double h1lambda_mt = align_flag ? src_h - static_cast(in_img_idy) : static_cast(ratio_h) * static_cast(out_img_idy) - static_cast(in_img_idy); double h2lambda_mt = static_cast(1.0) - h1lambda_mt; int64_t in_img_idx = align_flag ? static_cast(ratio_w * (out_img_idx + 0.5) - 0.5) : static_cast(ratio_w * out_img_idx); in_img_idx = (in_img_idx > 0) ? in_img_idx : 0; int64_t w_id = (in_img_idx < in_img_w - 1) ? 1 : 0; double src_w = static_cast(ratio_w) * static_cast(out_img_idx + 0.5) - static_cast(0.5); src_w = (src_w > static_cast(0)) ? src_w : static_cast(0); double w1lambda_mt = align_flag ? src_w - static_cast(in_img_idx) : static_cast(ratio_w) * static_cast(out_img_idx) - static_cast(in_img_idx); double w2lambda_mt = static_cast(1.0) - w1lambda_mt; T d1lambda = static_cast(d1lambda_mt); T d2lambda = static_cast(d2lambda_mt); T h1lambda = static_cast(h1lambda_mt); T h2lambda = static_cast(h2lambda_mt); T w1lambda = static_cast(w1lambda_mt); T w2lambda = static_cast(w2lambda_mt); if (data_layout == DataLayout::NCHW) { int64_t in_pos1_idx = static_cast(out_id_h) * input_w + static_cast(channel_id) * in_img_size + (static_cast(in_img_idt) * in_img_h + in_img_idy) * in_img_w + in_img_idx; T* in_pos1 = &in[in_pos1_idx]; int64_t in_pos2_idx = in_pos1_idx + static_cast(d_id) * in_img_h * in_img_w; T* in_pos2 = &in[in_pos2_idx]; const T* out_pos = &out[out_id_h * output_w + out_id_w]; // trilinear interpolation grad CudaAtomicAdd(&in_pos1[0], d2lambda * h2lambda * w2lambda * out_pos[0]); CudaAtomicAdd(&in_pos1[w_id], d2lambda * h2lambda * w1lambda * out_pos[0]); CudaAtomicAdd(&in_pos1[h_id * in_img_w], d2lambda * h1lambda * w2lambda * out_pos[0]); CudaAtomicAdd(&in_pos1[h_id * in_img_w + w_id], d2lambda * h1lambda * w1lambda * out_pos[0]); CudaAtomicAdd(&in_pos2[0], d1lambda * h2lambda * w2lambda * out_pos[0]); CudaAtomicAdd(&in_pos2[w_id], d1lambda * h2lambda * w1lambda * out_pos[0]); CudaAtomicAdd(&in_pos2[h_id * in_img_w], d1lambda * h1lambda * w2lambda * out_pos[0]); CudaAtomicAdd(&in_pos2[h_id * in_img_w + w_id], d1lambda * h1lambda * w1lambda * out_pos[0]); } else { int64_t in_pos1_idx = static_cast(out_id_h) * input_w + static_cast(in_img_idt) * in_img_h * in_img_w * num_channels + static_cast(in_img_idy) * in_img_w * num_channels + static_cast(in_img_idx) * num_channels + channel_id; T* in_pos1 = &in[in_pos1_idx]; int64_t in_pos2_idx = in_pos1_idx + static_cast(d_id) * in_img_h * in_img_w * num_channels; T* in_pos2 = &in[in_pos2_idx]; const T* out_pos = &out[out_id_h * output_w + out_id_w]; // trilinear interpolation grad CudaAtomicAdd(&in_pos1[0], d2lambda * h2lambda * w2lambda * out_pos[0]); CudaAtomicAdd(&in_pos1[w_id * num_channels], d2lambda * h2lambda * w1lambda * out_pos[0]); CudaAtomicAdd(&in_pos1[h_id * in_img_w * num_channels], d2lambda * h1lambda * w2lambda * out_pos[0]); CudaAtomicAdd( &in_pos1[h_id * in_img_w * num_channels + w_id * num_channels], d2lambda * h1lambda * w1lambda * out_pos[0]); CudaAtomicAdd(&in_pos2[0], d1lambda * h2lambda * w2lambda * out_pos[0]); CudaAtomicAdd(&in_pos2[w_id * num_channels], d1lambda * h2lambda * w1lambda * out_pos[0]); CudaAtomicAdd(&in_pos2[h_id * in_img_w * num_channels], d1lambda * h1lambda * w2lambda * out_pos[0]); CudaAtomicAdd( &in_pos2[h_id * in_img_w * num_channels + w_id * num_channels], d1lambda * h1lambda * w1lambda * out_pos[0]); } } } template __global__ void KeNearestNeighbor3DInterpBw(T* in, const size_t in_img_d, const size_t in_img_h, const size_t in_img_w, const size_t input_h, const size_t input_w, const T* out, const size_t out_img_d, const size_t out_img_h, const size_t out_img_w, const size_t output_h, const size_t output_w, const size_t num_channels, const MT ratio_d, const MT ratio_h, const MT ratio_w, const bool align_corners, const DataLayout data_layout) { int64_t nthreads = output_h * output_w; int64_t tid = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; int64_t stride = static_cast(blockDim.x) * gridDim.x; for (; tid < nthreads; tid += stride) { int64_t out_id_h = tid / output_w; int64_t out_id_w = tid % output_w; int64_t in_img_size = input_w / num_channels; int64_t out_img_size = output_w / num_channels; int64_t channel_id, out_img_idt, out_img_idy, out_img_idx; if (data_layout == DataLayout::NCHW) { channel_id = out_id_w / out_img_size; out_img_idt = (out_id_w % out_img_size) / out_img_h / out_img_w; out_img_idy = ((out_id_w % out_img_size) / out_img_w) % out_img_h; out_img_idx = tid % out_img_w; } else { out_img_idt = out_id_w / (out_img_h * out_img_w * num_channels); out_img_idy = out_id_w % (out_img_h * out_img_w * num_channels) / (out_img_w * num_channels); out_img_idx = out_id_w % (out_img_w * num_channels) / num_channels; channel_id = tid % num_channels; } int64_t in_img_idt = (align_corners) ? static_cast(ratio_d * out_img_idt + 0.5) : static_cast(ratio_d * out_img_idt); int64_t in_img_idy = (align_corners) ? static_cast(ratio_h * out_img_idy + 0.5) : static_cast(ratio_h * out_img_idy); int64_t in_img_idx = (align_corners) ? static_cast(ratio_w * out_img_idx + 0.5) : static_cast(ratio_w * out_img_idx); int64_t in_pos_idx; if (data_layout == DataLayout::NCHW) { in_pos_idx = static_cast(out_id_h) * input_w + static_cast(channel_id) * in_img_size + static_cast(in_img_idt) * in_img_h * in_img_w + static_cast(in_img_idy) * in_img_w + static_cast(in_img_idx); } else { in_pos_idx = static_cast(out_id_h) * input_w + static_cast(in_img_idt) * in_img_h * in_img_w * num_channels + static_cast(in_img_idy) * in_img_w * num_channels + static_cast(in_img_idx) * num_channels + static_cast(channel_id); } T* in_pos = &in[in_pos_idx]; const T out_pos = out[out_id_h * output_w + out_id_w]; CudaAtomicAdd(in_pos, out_pos); } } // Helper function to compute weights for backward pass template __device__ __forceinline__ void ComputeWeightsBw( T* wt_ptr, const MT scale, int interp_size, const InterpFilter& interp_filter, MT xmin_m_center, int xsize) { MT invscale = (scale >= 1.0) ? 1.0 / scale : 1.0; MT total_w = 0.0; int j = 0; for (j = 0; j < xsize; j++) { MT w = interp_filter((j + xmin_m_center + static_cast(0.5)) * invscale); wt_ptr[j] = static_cast(w); total_w += w; } for (j = 0; j < xsize; j++) { if (total_w != 0.0) { wt_ptr[j] = static_cast(static_cast(wt_ptr[j]) / total_w); } } for (; j < interp_size; j++) { wt_ptr[j] = static_cast(0.0); } } template __global__ void KeInterpAABwNCHW(T* in_grad, const int64_t in_img_h, const int64_t in_img_w, const T* out_grad, const int64_t out_img_h, const int64_t out_img_w, const int64_t n, const int64_t c, const MT ratio_h, const MT ratio_w, const InterpFilter& interp_filter) { const int64_t out_img_idx = static_cast(threadIdx.x) + blockIdx.x * blockDim.x; const int64_t out_img_idy = static_cast(threadIdx.y) + blockIdx.y * blockDim.y; if (out_img_idx >= out_img_w || out_img_idy >= out_img_h) { return; } MT scale_h = ratio_h; MT scale_w = ratio_w; const MT support_h = (scale_h >= 1.0) ? (interp_filter.size * 0.5) * scale_h : interp_filter.size * 0.5; const MT support_w = (scale_w >= 1.0) ? (interp_filter.size * 0.5) * scale_w : interp_filter.size * 0.5; const int interp_height = static_cast(ceilf(support_h)) * 2 + 1; const int interp_width = static_cast(ceilf(support_w)) * 2 + 1; // Use shared memory for weights extern __shared__ int smem[]; T* wx = reinterpret_cast(smem) + interp_width * threadIdx.x; T* wy = reinterpret_cast(smem) + interp_width * blockDim.x + interp_height * threadIdx.y; // Compute weights and kernel spans int xmin, xsize, ymin, ysize; MT xcenter, ycenter; ComputeWeightsSpan( out_img_idx, in_img_w, scale_w, support_w, &xmin, &xsize, &xcenter); ComputeWeightsSpan( out_img_idy, in_img_h, scale_h, support_h, &ymin, &ysize, &ycenter); if (threadIdx.y == 0) { ComputeWeightsBw( wx, scale_w, interp_width, interp_filter, xmin - xcenter, xsize); } if (threadIdx.x == 0) { ComputeWeightsBw( wy, scale_h, interp_height, interp_filter, ymin - ycenter, ysize); } __syncthreads(); for (int64_t i = blockIdx.z; i < n * c; i += gridDim.z) { const MT grad_out = static_cast(out_grad[i * out_img_h * out_img_w + out_img_idy * out_img_w + out_img_idx]); // Backward pass: distribute gradient to input pixels according to weights for (int y = 0; y < ysize; y++) { const MT wy_val = static_cast(wy[y]); for (int x = 0; x < xsize; x++) { const MT wx_val = static_cast(wx[x]); const MT grad = grad_out * wy_val * wx_val; const int64_t in_idx = i * in_img_h * in_img_w + (ymin + y) * in_img_w + (xmin + x); CudaAtomicAdd(&in_grad[in_idx], static_cast(grad)); } } } } template __global__ void KeInterpAABwNHWC(T* in_grad, const int64_t in_img_h, const int64_t in_img_w, const T* out_grad, const int64_t out_img_h, const int64_t out_img_w, const int64_t n, const int64_t c, const MT ratio_h, const MT ratio_w, const InterpFilter& interp_filter) { const int64_t out_img_idx = static_cast(threadIdx.x) + blockIdx.x * blockDim.x; const int64_t out_img_idy = static_cast(threadIdx.y) + blockIdx.y * blockDim.y; if (out_img_idx >= out_img_w || out_img_idy >= out_img_h) { return; } MT scale_h = ratio_h; MT scale_w = ratio_w; const MT support_h = (scale_h >= 1.0) ? (interp_filter.size * 0.5) * scale_h : interp_filter.size * 0.5; const MT support_w = (scale_w >= 1.0) ? (interp_filter.size * 0.5) * scale_w : interp_filter.size * 0.5; const int interp_height = static_cast(ceilf(support_h)) * 2 + 1; const int interp_width = static_cast(ceilf(support_w)) * 2 + 1; // Use shared memory for weights extern __shared__ int smem[]; T* wx = reinterpret_cast(smem) + interp_width * threadIdx.x; T* wy = reinterpret_cast(smem) + interp_width * blockDim.x + interp_height * threadIdx.y; // Compute weights and kernel spans int xmin, xsize, ymin, ysize; MT xcenter, ycenter; ComputeWeightsSpan( out_img_idx, in_img_w, scale_w, support_w, &xmin, &xsize, &xcenter); ComputeWeightsSpan( out_img_idy, in_img_h, scale_h, support_h, &ymin, &ysize, &ycenter); if (threadIdx.y == 0) { ComputeWeightsBw( wx, scale_w, interp_width, interp_filter, xmin - xcenter, xsize); } if (threadIdx.x == 0) { ComputeWeightsBw( wy, scale_h, interp_height, interp_filter, ymin - ycenter, ysize); } __syncthreads(); // Process each batch for (int64_t i = blockIdx.z; i < n; i += gridDim.z) { for (int64_t ch = 0; ch < c; ch++) { const MT grad_out = static_cast(out_grad[(i * out_img_h * out_img_w + out_img_idy * out_img_w + out_img_idx) * c + ch]); // Backward pass: distribute gradient to input pixels according to weights for (int y = 0; y < ysize; y++) { const MT wy_val = static_cast(wy[y]); for (int x = 0; x < xsize; x++) { const MT wx_val = static_cast(wx[x]); const MT grad = grad_out * wy_val * wx_val; const int64_t in_idx = (i * in_img_h * in_img_w + (ymin + y) * in_img_w + (xmin + x)) * c + ch; CudaAtomicAdd(&in_grad[in_idx], static_cast(grad)); } } } } } // No shared memory version of AA interpolation backward kernel for large ratio // values. Each thread computes weights on-the-fly without using shared memory template __global__ void KeInterpAABwNCHWNoSharedMem(T* in_grad, const int64_t in_img_h, const int64_t in_img_w, const T* out_grad, const int64_t out_img_h, const int64_t out_img_w, const int64_t n, const int64_t c, const MT ratio_h, const MT ratio_w, const InterpFilter& interp_filter) { const int64_t out_img_idx = static_cast(threadIdx.x) + blockIdx.x * blockDim.x; const int64_t out_img_idy = static_cast(threadIdx.y) + blockIdx.y * blockDim.y; if (out_img_idx >= out_img_w || out_img_idy >= out_img_h) { return; } MT scale_h = ratio_h; MT scale_w = ratio_w; const MT support_h = (scale_h >= 1.0) ? (interp_filter.size * 0.5) * scale_h : interp_filter.size * 0.5; const MT support_w = (scale_w >= 1.0) ? (interp_filter.size * 0.5) * scale_w : interp_filter.size * 0.5; // Compute weights span int xmin, xsize, ymin, ysize; MT xcenter, ycenter; ComputeWeightsSpan( out_img_idx, in_img_w, scale_w, support_w, &xmin, &xsize, &xcenter); ComputeWeightsSpan( out_img_idy, in_img_h, scale_h, support_h, &ymin, &ysize, &ycenter); // Compute weight normalization factors MT total_wx = ComputeWeightSum(scale_w, interp_filter, xmin - xcenter, xsize); MT total_wy = ComputeWeightSum(scale_h, interp_filter, ymin - ycenter, ysize); for (int64_t i = blockIdx.z; i < n * c; i += gridDim.z) { const MT grad_out = static_cast(out_grad[i * out_img_h * out_img_w + out_img_idy * out_img_w + out_img_idx]); // Backward pass: distribute gradient to input pixels for (int y = 0; y < ysize; y++) { MT wy_val = ComputeSingleWeightBwNormalized( scale_h, interp_filter, ymin - ycenter, y, total_wy); for (int x = 0; x < xsize; x++) { MT wx_val = ComputeSingleWeightBwNormalized( scale_w, interp_filter, xmin - xcenter, x, total_wx); const MT grad = grad_out * wy_val * wx_val; const int64_t in_idx = i * in_img_h * in_img_w + (ymin + y) * in_img_w + (xmin + x); CudaAtomicAdd(&in_grad[in_idx], static_cast(grad)); } } } } template __global__ void KeInterpAABwNHWCNoSharedMem(T* in_grad, const int64_t in_img_h, const int64_t in_img_w, const T* out_grad, const int64_t out_img_h, const int64_t out_img_w, const int64_t n, const int64_t c, const MT ratio_h, const MT ratio_w, const InterpFilter& interp_filter) { const int64_t out_img_idx = static_cast(threadIdx.x) + blockIdx.x * blockDim.x; const int64_t out_img_idy = static_cast(threadIdx.y) + blockIdx.y * blockDim.y; if (out_img_idx >= out_img_w || out_img_idy >= out_img_h) { return; } MT scale_h = ratio_h; MT scale_w = ratio_w; const MT support_h = (scale_h >= 1.0) ? (interp_filter.size * 0.5) * scale_h : interp_filter.size * 0.5; const MT support_w = (scale_w >= 1.0) ? (interp_filter.size * 0.5) * scale_w : interp_filter.size * 0.5; // Compute weights span int xmin, xsize, ymin, ysize; MT xcenter, ycenter; ComputeWeightsSpan( out_img_idx, in_img_w, scale_w, support_w, &xmin, &xsize, &xcenter); ComputeWeightsSpan( out_img_idy, in_img_h, scale_h, support_h, &ymin, &ysize, &ycenter); // Compute weight normalization factors MT total_wx = ComputeWeightSum(scale_w, interp_filter, xmin - xcenter, xsize); MT total_wy = ComputeWeightSum(scale_h, interp_filter, ymin - ycenter, ysize); for (int64_t i = blockIdx.z; i < n; i += gridDim.z) { for (int64_t ch = 0; ch < c; ch++) { const MT grad_out = static_cast(out_grad[(i * out_img_h * out_img_w + out_img_idy * out_img_w + out_img_idx) * c + ch]); // Backward pass: distribute gradient to input pixels for (int y = 0; y < ysize; y++) { MT wy_val = ComputeSingleWeightBwNormalized( scale_h, interp_filter, ymin - ycenter, y, total_wy); for (int x = 0; x < xsize; x++) { MT wx_val = ComputeSingleWeightBwNormalized( scale_w, interp_filter, xmin - xcenter, x, total_wx); const MT grad = grad_out * wy_val * wx_val; const int64_t in_idx = (i * in_img_h * in_img_w + (ymin + y) * in_img_w + (xmin + x)) * c + ch; CudaAtomicAdd(&in_grad[in_idx], static_cast(grad)); } } } } } template static void Interpolate1DCUDABwd( const Context& dev_ctx, const DenseTensor& input, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& output_grad, const std::string& data_layout_str, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* input_grad) { const DataLayout data_layout = StringToDataLayout(data_layout_str); int64_t n, c, in_d, in_h, in_w; funcs::ExtractNCDWH(input.dims(), data_layout, &n, &c, &in_d, &in_h, &in_w); double scale_w = -1; if (scale_tensor) { auto scale_data = funcs::get_new_data_from_tensor(scale_tensor.get_ptr()); scale_w = scale_data[0]; PADDLE_ENFORCE_EQ( scale_w > 0, true, errors::InvalidArgument( "The scale_w in input 'Scale' Tensor of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_w)); } else { if (scale.size() > 0) { scale_w = scale[0]; PADDLE_ENFORCE_EQ( scale_w > 0, true, errors::InvalidArgument( "The scale_w in Attr(scale) of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_w)); } } if (scale_w > 0.) { out_w = static_cast(in_w * scale_w); } if (out_size) { DenseTensor sizes; Copy(dev_ctx, *out_size, CPUPlace(), true, &sizes); auto size_data = sizes.data(); out_w = size_data[0]; } if (size_tensor && size_tensor->size() > 0) { // have size tensor auto new_size = funcs::get_new_shape(size_tensor.get()); out_w = new_size[0]; } auto* output_grad_data = output_grad.data(); DDim dim_grad; if (data_layout == DataLayout::NCHW) { dim_grad = {n, c, in_w}; } else { dim_grad = {n, in_w, c}; } input_grad->Resize(dim_grad); auto* input_grad_data = dev_ctx.template Alloc(input_grad); funcs::SetConstant zero; zero(dev_ctx, input_grad, static_cast(0.0)); if (in_w == out_w) { Copy(dev_ctx, output_grad, dev_ctx.GetPlace(), false, input_grad); return; } using MT = typename MPTypeTrait::Type; MT ratio_w = funcs::AreaPixelComputeScale(in_w, out_w, align_corners, scale_w); int64_t in_cw = c * in_w; int64_t out_cw = c * out_w; auto pixelNum = n * out_cw; backends::gpu::GpuLaunchConfig config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, pixelNum); if ("linear" == interp_method) { KeLinearInterpBw<<>>(input_grad_data, in_w, in_cw, output_grad_data, out_w, n, out_cw, c, ratio_w, align_corners, align_mode, data_layout); } } template static void Interpolate2DCUDABwd( const Context& dev_ctx, const DenseTensor& input, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& output_grad, const std::string& data_layout_str, int64_t out_h, int64_t out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* input_grad) { const DataLayout data_layout = StringToDataLayout(data_layout_str); int64_t n, c, in_d, in_h, in_w; funcs::ExtractNCDWH(input.dims(), data_layout, &n, &c, &in_d, &in_h, &in_w); double scale_h = -1; double scale_w = -1; if (scale_tensor) { auto scale_data = funcs::get_new_data_from_tensor(scale_tensor.get_ptr()); if (scale_data.size() > 1) { scale_h = scale_data[0]; scale_w = scale_data[1]; } else { scale_h = scale_data[0]; scale_w = scale_data[0]; } PADDLE_ENFORCE_EQ( scale_w > 0, true, errors::InvalidArgument( "The scale_w in input 'Scale' Tensor of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_w)); PADDLE_ENFORCE_EQ( scale_h > 0, true, errors::InvalidArgument( "The scale_h in input 'Scale' Tensor of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_h)); } else { if (scale.size() > 1) { scale_w = scale[1]; scale_h = scale[0]; PADDLE_ENFORCE_EQ( scale_w > 0, true, errors::InvalidArgument( "The scale_w in Attr(scale) of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_w)); PADDLE_ENFORCE_EQ( scale_h > 0, true, errors::InvalidArgument( "The scale_h in Attr(scale) of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_h)); } } if (scale_w > 0. && scale_h > 0.) { out_h = static_cast(in_h * scale_h); out_w = static_cast(in_w * scale_w); } if (out_size) { DenseTensor sizes; Copy(dev_ctx, *out_size, CPUPlace(), true, &sizes); auto size_data = sizes.data(); out_h = size_data[0]; out_w = size_data[1]; } if (size_tensor && size_tensor->size() > 0) { // have size tensor auto new_size = funcs::get_new_shape(size_tensor.get()); out_h = new_size[0]; out_w = new_size[1]; } auto* output_grad_data = output_grad.data(); DDim dim_grad; if (data_layout == DataLayout::NCHW) { dim_grad = {n, c, in_h, in_w}; } else { dim_grad = {n, in_h, in_w, c}; } input_grad->Resize(dim_grad); auto* input_grad_data = dev_ctx.template Alloc(input_grad); funcs::SetConstant zero; zero(dev_ctx, input_grad, static_cast(0.0)); if (in_h == out_h && in_w == out_w) { Copy(dev_ctx, output_grad, dev_ctx.GetPlace(), false, input_grad); return; } using MT = typename std::conditional_t::value, float, typename MPTypeTrait::Type>; MT ratio_h = funcs::AreaPixelComputeScale(in_h, out_h, align_corners, scale_h); MT ratio_w = funcs::AreaPixelComputeScale(in_w, out_w, align_corners, scale_w); int64_t in_hw = in_h * in_w; int64_t out_hw = out_h * out_w; int64_t in_chw = c * in_hw; int64_t out_chw = c * out_hw; auto pixelNum = n * out_chw; backends::gpu::GpuLaunchConfig config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, pixelNum); if ("nearest" == interp_method) { if (data_layout == DataLayout::NCHW) { // get launch 3D config int64_t nc = n * c; int64_t total_size = static_cast(n) * c * in_h * in_w; backends::gpu::GpuLaunchConfig config_3d = backends::gpu::GetGpuLaunchConfig3D(dev_ctx, nc, out_h, out_w); if (static_cast(total_size) > std::numeric_limits::max()) { KeNearestNeighborInterpNCHWBw <<>>(input_grad_data, in_h, in_w, output_grad_data, out_h, out_w, nc, ratio_h, ratio_w, align_corners); } else { KeNearestNeighborInterpNCHWBw <<>>(input_grad_data, in_h, in_w, output_grad_data, out_h, out_w, nc, ratio_h, ratio_w, align_corners); } } else { int64_t cw = c * out_w; auto interp_divmods = funcs::FastDivModForInterpolate(c, out_chw, cw); KeNearestNeighborInterpBw<<>>(input_grad_data, in_h, in_w, n, in_chw, output_grad_data, out_h, out_w, n, out_chw, c, ratio_h, ratio_w, align_corners, interp_divmods); } } else if ("bilinear" == interp_method) { bool is_nchw = (data_layout == DataLayout::NCHW) ? true : false; bool optimize_flag = false; #ifndef __HIPCC__ optimize_flag = (in_h < (out_h >> 6) && in_w < (out_w >> 6)) ? true : ((in_h == 1 && in_w == 1) ? true : false); #endif if (optimize_flag & is_nchw) { KeBilinearInterpBwShareMemory<<>>(input_grad_data, in_h, in_w, output_grad_data, out_h, out_w, n, c, ratio_h, ratio_w, align_corners, align_mode, is_nchw); } else if (!optimize_flag & is_nchw) { const int64_t num_kernels = static_cast(n) * c * out_h * out_w; const int num_threads = std::min(dev_ctx.GetMaxThreadsPerBlock(), 1024); KeBilinearInterpNCHWBw <<(num_threads)), num_threads, 0, dev_ctx.stream()>>>(input_grad_data, in_h, in_w, out_h, out_w, n, c, ratio_h, ratio_w, output_grad_data, align_corners, align_mode); } else { int64_t cw = c * out_w; auto interp_divmods = funcs::FastDivModForInterpolate(c, out_chw, cw); KeBilinearInterpBw<<>>(input_grad_data, in_h, in_w, output_grad_data, out_h, out_w, n, out_chw, c, ratio_h, ratio_w, align_corners, align_mode, interp_divmods); } } else if ("bicubic" == interp_method) { constexpr int thread_per_block = 512; KeBicubicInterpBw <<>>( input_grad_data, in_h, in_w, n, in_chw, output_grad_data, out_h, out_w, n, out_chw, c, ratio_h, ratio_w, align_corners, data_layout); } } template static void InterpolateAA2DCUDABwd( const Context& dev_ctx, const DenseTensor& input, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& output_grad, const std::string& data_layout_str, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* input_grad) { if (input_grad && input_grad->numel() == 0) { dev_ctx.template Alloc(input_grad); return; } const DataLayout data_layout = StringToDataLayout(data_layout_str); int64_t n, c, in_d, in_h, in_w; funcs::ExtractNCDWH(input.dims(), data_layout, &n, &c, &in_d, &in_h, &in_w); double scale_h = -1; double scale_w = -1; if (size_tensor && size_tensor->size() > 0) { // have size tensor auto new_size = funcs::get_new_shape(size_tensor.get()); out_h = new_size[0]; out_w = new_size[1]; } else { if (scale_tensor) { auto scale_data = funcs::get_new_data_from_tensor(scale_tensor.get_ptr()); if (scale_data.size() > 1) { scale_h = scale_data[0]; scale_w = scale_data[1]; } else { scale_h = scale_data[0]; scale_w = scale_data[0]; } PADDLE_ENFORCE_EQ( scale_w > 0, true, errors::InvalidArgument( "The scale_w in input 'Scale' Tensor of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_w)); PADDLE_ENFORCE_EQ( scale_h > 0, true, errors::InvalidArgument( "The scale_h in input 'Scale' Tensor of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_h)); } else { if (scale.size() > 1) { scale_w = scale[1]; scale_h = scale[0]; PADDLE_ENFORCE_EQ( scale_w > 0, true, errors::InvalidArgument( "The scale_w in Attr(scale) of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_w)); PADDLE_ENFORCE_EQ( scale_h > 0, true, errors::InvalidArgument( "The scale_h in Attr(scale) of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_h)); } } if (scale_w > 0. && scale_h > 0.) { out_h = static_cast(in_h * scale_h); out_w = static_cast(in_w * scale_w); } if (out_size) { DenseTensor sizes; Copy(dev_ctx, *out_size, CPUPlace(), true, &sizes); auto size_data = sizes.data(); out_h = size_data[0]; out_w = size_data[1]; } } auto* output_grad_data = output_grad.data(); DDim dim_grad; if (data_layout == DataLayout::NCHW) { dim_grad = {n, c, in_h, in_w}; } else { dim_grad = {n, in_h, in_w, c}; } input_grad->Resize(dim_grad); auto* input_grad_data = dev_ctx.template Alloc(input_grad); funcs::SetConstant zero; zero(dev_ctx, input_grad, static_cast(0.0)); if (in_h == out_h && in_w == out_w) { Copy(dev_ctx, output_grad, dev_ctx.GetPlace(), false, input_grad); return; } using MT = typename std::conditional_t::value, float, typename MPTypeTrait::Type>; MT ratio_h = funcs::AreaPixelComputeScale(in_h, out_h, align_corners, scale_h); MT ratio_w = funcs::AreaPixelComputeScale(in_w, out_w, align_corners, scale_w); int64_t nc = static_cast(n) * c; // Lambda to launch AA interpolation backward kernel auto launch_aa_bw_kernel = [&](auto filter) { int device_id = dev_ctx.GetPlace().GetDeviceId(); auto& gpu_props = backends::gpu::GetDeviceProperties(device_id); // Use AAInterpLaunchConfig to compute block/grid dimensions with dynamic // adjustment for shared memory limits funcs::antialias::AAInterpLaunchConfig launch_config( out_h, out_w, nc, ratio_h, ratio_w, decltype(filter)::size, sizeof(T), gpu_props.sharedMemPerBlock, gpu_props.maxGridSize[2], static_cast(gpu_props.warpSize), false /* no buffer needed for backward */); dim3 block(launch_config.block_x, launch_config.block_y); dim3 grid(launch_config.grid_x, launch_config.grid_y, launch_config.grid_z); // Check if shared memory is sufficient, otherwise use no-shared-mem kernel if (launch_config.IsValid(gpu_props.sharedMemPerBlock)) { // Use shared memory optimized kernel if (data_layout == DataLayout::NCHW) { KeInterpAABwNCHW <<>>( input_grad_data, in_h, in_w, output_grad_data, out_h, out_w, n, c, ratio_h, ratio_w, filter); } else { KeInterpAABwNHWC <<>>( input_grad_data, in_h, in_w, output_grad_data, out_h, out_w, n, c, ratio_h, ratio_w, filter); } } else { // Shared memory insufficient, use on-the-fly weight computation kernel // Use simpler block/grid config without shared memory constraints int block_x = std::min(static_cast(gpu_props.warpSize), 32); int block_y = std::min(256 / block_x, 8); int grid_x = (out_w + block_x - 1) / block_x; int grid_y = (out_h + block_y - 1) / block_y; int grid_z = std::min(static_cast(nc), static_cast(gpu_props.maxGridSize[2])); dim3 block_noshmem(block_x, block_y); dim3 grid_noshmem(grid_x, grid_y, grid_z); if (data_layout == DataLayout::NCHW) { KeInterpAABwNCHWNoSharedMem <<>>( input_grad_data, in_h, in_w, output_grad_data, out_h, out_w, n, c, ratio_h, ratio_w, filter); } else { KeInterpAABwNHWCNoSharedMem <<>>( input_grad_data, in_h, in_w, output_grad_data, out_h, out_w, n, c, ratio_h, ratio_w, filter); } } }; if ("bilinear" == interp_method) { launch_aa_bw_kernel(funcs::antialias::BilinearFilterFunctor{}); } else if ("bicubic" == interp_method) { launch_aa_bw_kernel(funcs::antialias::BicubicFilterFunctor{}); } } template static void Interpolate3DCUDABwd( const Context& dev_ctx, const DenseTensor& input, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& output_grad, const std::string& data_layout_str, int out_d, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* input_grad) { const DataLayout data_layout = StringToDataLayout(data_layout_str); int64_t n, c, in_d, in_h, in_w; funcs::ExtractNCDWH(input.dims(), data_layout, &n, &c, &in_d, &in_h, &in_w); double scale_d = -1; double scale_h = -1; double scale_w = -1; if (scale_tensor) { auto scale_data = funcs::get_new_data_from_tensor(scale_tensor.get_ptr()); if (scale_data.size() > 2) { scale_d = scale_data[0]; scale_h = scale_data[1]; scale_w = scale_data[2]; } else { scale_d = scale_data[0]; scale_h = scale_data[0]; scale_w = scale_data[0]; } PADDLE_ENFORCE_EQ( scale_w > 0, true, errors::InvalidArgument( "The scale_w in input 'Scale' Tensor of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_w)); PADDLE_ENFORCE_EQ( scale_h > 0, true, errors::InvalidArgument( "The scale_h in input 'Scale' Tensor of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_h)); PADDLE_ENFORCE_EQ( scale_d > 0, true, errors::InvalidArgument( "The scale_d in input 'Scale' Tensor of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_d)); } else { if (scale.size() > 2) { scale_d = scale[0]; scale_h = scale[1]; scale_w = scale[2]; PADDLE_ENFORCE_EQ( scale_w > 0, true, errors::InvalidArgument( "The scale_w in Attr(scale) of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_w)); PADDLE_ENFORCE_EQ( scale_h > 0, true, errors::InvalidArgument( "The scale_h in Attr(scale) of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_h)); PADDLE_ENFORCE_EQ( scale_d > 0, true, errors::InvalidArgument( "The scale_d in Attr(scale) of Operator(interpolate) " "should be greater than 0, but received value is %d.", scale_d)); } } if (scale_d > 0. && scale_h > 0. && scale_w > 0.) { out_d = static_cast(in_d * scale_d); out_h = static_cast(in_h * scale_h); out_w = static_cast(in_w * scale_w); } if (out_size) { DenseTensor sizes; Copy(dev_ctx, *out_size, CPUPlace(), true, &sizes); auto size_data = sizes.data(); out_d = size_data[0]; out_h = size_data[1]; out_w = size_data[2]; } if (size_tensor && size_tensor->size() > 0) { // have size tensor auto new_size = funcs::get_new_shape(size_tensor.get()); out_d = new_size[0]; out_h = new_size[1]; out_w = new_size[2]; } auto* output_grad_data = output_grad.data(); DDim dim_grad; if (data_layout == DataLayout::NCHW) { dim_grad = {n, c, in_d, in_h, in_w}; } else { dim_grad = {n, in_d, in_h, in_w, c}; } input_grad->Resize(dim_grad); auto* input_grad_data = dev_ctx.template Alloc(input_grad); funcs::SetConstant zero; zero(dev_ctx, input_grad, static_cast(0.0)); if (in_d == out_d && in_h == out_h && in_w == out_w) { Copy(dev_ctx, output_grad, dev_ctx.GetPlace(), false, input_grad); return; } using MT = typename std::conditional_t::value, float, typename MPTypeTrait::Type>; MT ratio_d = funcs::AreaPixelComputeScale(in_d, out_d, align_corners, scale_d); MT ratio_h = funcs::AreaPixelComputeScale(in_h, out_h, align_corners, scale_h); MT ratio_w = funcs::AreaPixelComputeScale(in_w, out_w, align_corners, scale_w); int64_t in_dhw = in_d * in_h * in_w; int64_t out_dhw = out_d * out_h * out_w; int64_t in_cdhw = c * in_dhw; int64_t out_cdhw = c * out_dhw; auto pixelNum = n * out_cdhw; backends::gpu::GpuLaunchConfig config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, pixelNum); if ("trilinear" == interp_method) { KeTrilinearInterpBw<<>>(input_grad_data, in_d, in_h, in_w, n, in_cdhw, output_grad_data, out_d, out_h, out_w, n, out_cdhw, c, ratio_d, ratio_h, ratio_w, align_corners, align_mode, data_layout); } else if ("nearest" == interp_method) { KeNearestNeighbor3DInterpBw<<>>(input_grad_data, in_d, in_h, in_w, n, in_cdhw, output_grad_data, out_d, out_h, out_w, n, out_cdhw, c, ratio_d, ratio_h, ratio_w, align_corners, data_layout); } } template void InterpolateGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { if (x_grad && x_grad->numel() == 0) { dev_ctx.template Alloc(x_grad); return; } auto output_grad_dims = out_grad.dims(); if (output_grad_dims.size() == 3) { // 1D interpolation grad Interpolate1DCUDABwd(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_w, scale, interp_method, align_corners, align_mode, x_grad); } else if (output_grad_dims.size() == 4) { // 2D interpolation grad Interpolate2DCUDABwd(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_h, out_w, scale, interp_method, align_corners, align_mode, x_grad); } else if (output_grad_dims.size() == 5) { // 3D interpolation grad Interpolate3DCUDABwd(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_d, out_h, out_w, scale, interp_method, align_corners, align_mode, x_grad); } } template void BilinearInterpGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { InterpolateGradKernel(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_d, out_h, out_w, scale, interp_method, align_corners, align_mode, x_grad); } template void LegacyBilinearInterpGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, float scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { const auto& dim_x = x.dims(); std::vector scale_vec; if (scale > 0) { for (int i = 0; i < dim_x.size() - 2; i++) { scale_vec.push_back(scale); } } InterpolateGradKernel(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_d, out_h, out_w, scale_vec, interp_method, align_corners, align_mode, x_grad); } template void NearestInterpGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { InterpolateGradKernel(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_d, out_h, out_w, scale, interp_method, align_corners, align_mode, x_grad); } template void LegacyNearestInterpGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, float scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { const auto& dim_x = x.dims(); std::vector scale_vec; if (scale > 0) { for (int i = 0; i < dim_x.size() - 2; i++) { scale_vec.push_back(scale); } } InterpolateGradKernel(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_d, out_h, out_w, scale_vec, interp_method, align_corners, align_mode, x_grad); } template void TrilinearInterpGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { InterpolateGradKernel(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_d, out_h, out_w, scale, interp_method, align_corners, align_mode, x_grad); } template void LinearInterpGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { InterpolateGradKernel(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_d, out_h, out_w, scale, interp_method, align_corners, align_mode, x_grad); } template void BicubicInterpGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { InterpolateGradKernel(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_d, out_h, out_w, scale, interp_method, align_corners, align_mode, x_grad); } template void InterpAntialiasGradKernel( const Context& dev_ctx, const DenseTensor& x, const optional& out_size, const optional>& size_tensor, const optional& scale_tensor, const DenseTensor& out_grad, const std::string& data_layout, int out_d, int out_h, int out_w, const std::vector& scale, const std::string& interp_method, bool align_corners, int align_mode, DenseTensor* x_grad) { InterpolateAA2DCUDABwd(dev_ctx, x, out_size, size_tensor, scale_tensor, out_grad, data_layout, out_h, out_w, scale, interp_method, align_corners, align_mode, x_grad); } } // namespace phi PD_REGISTER_KERNEL(interp_antialias_grad, GPU, ALL_LAYOUT, phi::InterpAntialiasGradKernel, float, double, phi::float16, phi::bfloat16) { kernel->InputAt(1).SetBackend(phi::Backend::CPU); kernel->InputAt(2).SetBackend(phi::Backend::ALL_BACKEND); kernel->InputAt(3).SetBackend(phi::Backend::ALL_BACKEND); } PD_REGISTER_KERNEL(bilinear_interp_grad, GPU, ALL_LAYOUT, phi::BilinearInterpGradKernel, float, double, phi::float16, phi::bfloat16) { kernel->InputAt(1).SetBackend(phi::Backend::CPU); kernel->InputAt(2).SetBackend(phi::Backend::ALL_BACKEND); kernel->InputAt(3).SetBackend(phi::Backend::ALL_BACKEND); } PD_REGISTER_KERNEL(legacy_bilinear_interp_grad, GPU, ALL_LAYOUT, phi::LegacyBilinearInterpGradKernel, float, double, phi::float16, phi::bfloat16) { kernel->InputAt(1).SetBackend(phi::Backend::CPU); kernel->InputAt(2).SetBackend(phi::Backend::ALL_BACKEND); kernel->InputAt(3).SetBackend(phi::Backend::ALL_BACKEND); } PD_REGISTER_KERNEL(nearest_interp_grad, GPU, ALL_LAYOUT, phi::NearestInterpGradKernel, float, double, phi::float16, phi::bfloat16) { kernel->InputAt(1).SetBackend(phi::Backend::CPU); kernel->InputAt(2).SetBackend(phi::Backend::ALL_BACKEND); kernel->InputAt(3).SetBackend(phi::Backend::ALL_BACKEND); } PD_REGISTER_KERNEL(legacy_nearest_interp_grad, GPU, ALL_LAYOUT, phi::LegacyNearestInterpGradKernel, float, double, phi::float16, phi::bfloat16) { kernel->InputAt(1).SetBackend(phi::Backend::CPU); kernel->InputAt(2).SetBackend(phi::Backend::ALL_BACKEND); kernel->InputAt(3).SetBackend(phi::Backend::ALL_BACKEND); } PD_REGISTER_KERNEL(trilinear_interp_grad, GPU, ALL_LAYOUT, phi::TrilinearInterpGradKernel, float, double, phi::float16, phi::bfloat16) { kernel->InputAt(1).SetBackend(phi::Backend::CPU); kernel->InputAt(2).SetBackend(phi::Backend::ALL_BACKEND); kernel->InputAt(3).SetBackend(phi::Backend::ALL_BACKEND); } PD_REGISTER_KERNEL(linear_interp_grad, GPU, ALL_LAYOUT, phi::LinearInterpGradKernel, float, double, phi::float16, phi::bfloat16) { kernel->InputAt(1).SetBackend(phi::Backend::CPU); kernel->InputAt(2).SetBackend(phi::Backend::ALL_BACKEND); kernel->InputAt(3).SetBackend(phi::Backend::ALL_BACKEND); } PD_REGISTER_KERNEL(bicubic_interp_grad, GPU, ALL_LAYOUT, phi::BicubicInterpGradKernel, float, double, phi::float16, phi::bfloat16) { kernel->InputAt(1).SetBackend(phi::Backend::CPU); kernel->InputAt(2).SetBackend(phi::Backend::ALL_BACKEND); kernel->InputAt(3).SetBackend(phi::Backend::ALL_BACKEND); }