// Copyright (c) 2025 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/common/enforce.h" #include "paddle/common/layout.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/common/amp_type_traits.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/cpu/conv_util.h" #include "paddle/phi/kernels/full_kernel.h" #include "paddle/phi/kernels/funcs/batch_norm_utils.h" #include "paddle/phi/kernels/funcs/common_shape.h" #include "paddle/phi/kernels/funcs/math_function.h" #include "paddle/phi/kernels/gpu/depthwise_conv.h" #include "paddle/phi/kernels/reduce_sum_kernel.h" namespace phi { constexpr int CUDA_NUM_THREADS = 1024; constexpr int CUDA_WARP_SIZE = 32; template __global__ void DWConv3dBwdInputKernel(const T* grad_output, T* grad_input, const T* weight, const int batch_size, const int input_channels, const int input_depth, int input_height, int input_width, const int output_channels, const int output_depth, int output_height, int output_width, const int kernel_t_in, int kernel_h_in, int kernel_w_in, const int stride_t_in, int stride_h_in, int stride_w_in, const int padding_t, int padding_h, int padding_w, const int dilation_t_in, int dilation_h_in, int dilation_w_in) { const int kernel_t = kKnownKernelT > 0 ? kKnownKernelT : kernel_t_in; const int kernel_h = kKnownKernelH > 0 ? kKnownKernelH : kernel_h_in; const int kernel_w = kKnownKernelW > 0 ? kKnownKernelW : kernel_w_in; const int dilation_t = kKnownDilationT > 0 ? kKnownDilationT : dilation_t_in; const int dilation_h = kKnownDilationH > 0 ? kKnownDilationH : dilation_h_in; const int dilation_w = kKnownDilationW > 0 ? kKnownDilationW : dilation_w_in; const int stride_t = kKnownStrideT > 0 ? kKnownStrideT : stride_t_in; const int stride_h = kKnownStrideH > 0 ? kKnownStrideH : stride_h_in; const int stride_w = kKnownStrideW > 0 ? kKnownStrideW : stride_w_in; const int channel_multiplier = output_channels / input_channels; const int64_t num_input = static_cast(batch_size) * input_channels * input_depth * input_height * input_width; const int64_t i_stride_c = static_cast(input_depth) * input_height * input_width; const int64_t i_stride_d = static_cast(input_height) * input_width; const int i_stride_h = input_width; const int64_t o_stride_c = static_cast(output_depth) * output_height * output_width; const int64_t o_stride_d = static_cast(output_height) * output_width; const int o_stride_h = output_width; const int64_t w_stride_c = static_cast(kernel_t) * kernel_h * kernel_w; for (int64_t index = static_cast(blockIdx.x) * static_cast(blockDim.x) + static_cast(threadIdx.x); index < num_input; index += static_cast(blockDim.x) * static_cast(gridDim.x)) { int64_t temp = index; const int in_col = temp % input_width; temp /= input_width; const int in_row = temp % input_height; temp /= input_height; const int in_frame = temp % input_depth; temp /= input_depth; const int in_channel = temp % input_channels; const int batch = temp / input_channels; const int64_t out_col_end = static_cast(in_col) + padding_w; const int64_t out_row_end = static_cast(in_row) + padding_h; const int64_t out_frame_end = static_cast(in_frame) + padding_t; AccT sum = 0; const T* weight_ptr = weight + static_cast(in_channel) * channel_multiplier * w_stride_c; const T* gout_base = grad_output + static_cast(batch) * output_channels * o_stride_c; for (int k_chn = 0; k_chn < channel_multiplier; ++k_chn) { const T* gout_ptr = gout_base + (static_cast(in_channel) * channel_multiplier + k_chn) * o_stride_c; for (int k_frame = 0; k_frame < kernel_t; ++k_frame) { const int64_t out_frame_raw = out_frame_end - static_cast(k_frame) * dilation_t; const int64_t out_frame = out_frame_raw / stride_t; if (out_frame * stride_t == out_frame_raw && out_frame >= 0 && out_frame < output_depth) { for (int k_row = 0; k_row < kernel_h; ++k_row) { const int64_t out_row_raw = out_row_end - static_cast(k_row) * dilation_h; const int64_t out_row = out_row_raw / stride_h; if (out_row * stride_h == out_row_raw && out_row >= 0 && out_row < output_height) { for (int k_col = 0; k_col < kernel_w; ++k_col) { const int64_t out_col_raw = out_col_end - static_cast(k_col) * dilation_w; const int64_t out_col = out_col_raw / stride_w; if (out_col * stride_w == out_col_raw && out_col >= 0 && out_col < output_width) { const int64_t w_offset = k_chn * w_stride_c + static_cast(k_frame) * kernel_h * kernel_w + static_cast(k_row) * kernel_w + k_col; T val_w = weight_ptr[w_offset]; const int64_t out_offset = out_frame * o_stride_d + out_row * o_stride_h + out_col; T val_go = gout_ptr[out_offset]; sum += static_cast(val_w) * static_cast(val_go); } } } } } } } grad_input[index] = static_cast(sum); } } template __global__ void DWConv3dBwdWeightKernel(const T* grad_output, const T* input, T* grad_weight, const int batch_size, const int input_channels, const int input_depth, int input_height, int input_width, const int output_channels, const int output_depth, int output_height, int output_width, const int kernel_t, int kernel_h, int kernel_w, const int stride_t, int stride_h_in, int stride_w_in, const int padding_t, int padding_h, int padding_w, const int dilation_t, int dilation_h, int dilation_w) { const int stride_h = kKnownStrideH > 0 ? kKnownStrideH : stride_h_in; const int stride_w = kKnownStrideW > 0 ? kKnownStrideW : stride_w_in; const int k_c = output_channels; const int k_col = blockIdx.x % kernel_w; const int k_row = (blockIdx.x / kernel_w) % kernel_h; const int k_frame = (blockIdx.x / kernel_w / kernel_h) % kernel_t; const int k_channel = blockIdx.x / kernel_w / kernel_h / kernel_t; if (k_channel >= k_c) return; const int channel_multiplier = output_channels / input_channels; const int in_channel = k_channel / channel_multiplier; extern __shared__ char smem_raw[]; T* sdata = reinterpret_cast(smem_raw); const int laneid = threadIdx.x % CUDA_WARP_SIZE; const int warpid = threadIdx.x / CUDA_WARP_SIZE; const int nwarps = blockDim.x / CUDA_WARP_SIZE; const int64_t i_stride_c = static_cast(input_depth) * input_height * input_width; const int64_t i_stride_d = static_cast(input_height) * input_width; const int i_stride_h = input_width; const int64_t o_stride_c = static_cast(output_depth) * output_height * output_width; const int64_t o_stride_d = static_cast(output_height) * output_width; const int o_stride_h = output_width; AccT grad = 0; int64_t batch = warpid / output_depth; int64_t gout_frame = warpid - batch * output_depth; const int64_t total_outer_loops = static_cast(batch_size) * output_depth; for (int64_t outer_pos = warpid; outer_pos < total_outer_loops; outer_pos += nwarps, gout_frame += nwarps) { while (gout_frame >= output_depth) { gout_frame -= output_depth; batch++; } const int64_t in_frame = gout_frame * stride_t + static_cast(k_frame) * dilation_t - padding_t; if (in_frame >= 0 && in_frame < input_depth) { const T* gout_ptr = grad_output + batch * (output_channels * o_stride_c) + k_channel * o_stride_c + gout_frame * o_stride_d; const T* input_ptr = input + batch * (input_channels * i_stride_c) + in_channel * i_stride_c + in_frame * i_stride_d; const T* gout_ptr_lane = gout_ptr + laneid; int gout_row = laneid / output_width; int gout_col = laneid % output_width; for (; gout_row < output_height;) { const AccT op1 = static_cast(*gout_ptr_lane); gout_ptr_lane += CUDA_WARP_SIZE; const int64_t in_col = static_cast(gout_col) * stride_w + static_cast(k_col) * dilation_w - padding_w; const int64_t in_row = static_cast(gout_row) * stride_h + static_cast(k_row) * dilation_h - padding_h; AccT op2 = 0; if (in_row >= 0 && in_row < input_height && in_col >= 0 && in_col < input_width) { const int64_t in_pos = static_cast(in_row) * i_stride_h + in_col; op2 = static_cast(input_ptr[in_pos]); } grad += op1 * op2; gout_col += CUDA_WARP_SIZE; while (gout_col >= output_width) { gout_col -= output_width; gout_row++; } } } } sdata[threadIdx.x] = static_cast(grad); __syncthreads(); for (int i = blockDim.x / 2; i >= 1; i >>= 1) { if (threadIdx.x < i) { AccT val = static_cast(sdata[threadIdx.x]) + static_cast(sdata[threadIdx.x + i]); sdata[threadIdx.x] = static_cast(val); } __syncthreads(); } if (threadIdx.x == 0) { grad_weight[blockIdx.x] = sdata[0]; } } template void LaunchDepthwiseConv3dBackwardCompatible(const Context& dev_ctx, const DenseTensor& input, const DenseTensor& filter, const DenseTensor& out_grad, const std::vector& strides, const std::vector& paddings, const std::vector& dilations, const std::string& data_format, DenseTensor* input_grad, DenseTensor* filter_grad, DenseTensor* bias_grad) { const bool channel_last = (data_format == "NDHWC"); DenseTensor input_ncdhw; DenseTensor out_grad_ncdhw; const DenseTensor& filter_ncdhw = filter; DenseTensor input_grad_ncdhw_tmp; DenseTensor* input_grad_ncdhw_ptr = nullptr; DenseTensor* filter_grad_ncdhw_ptr = nullptr; if (channel_last) { ResizeToChannelFirst(dev_ctx, &input, &input_ncdhw); TransToChannelFirst(dev_ctx, &input, &input_ncdhw); ResizeToChannelFirst(dev_ctx, &out_grad, &out_grad_ncdhw); TransToChannelFirst(dev_ctx, &out_grad, &out_grad_ncdhw); if (input_grad) { ResizeToChannelFirst( dev_ctx, input_grad, &input_grad_ncdhw_tmp); dev_ctx.template Alloc(&input_grad_ncdhw_tmp); input_grad_ncdhw_ptr = &input_grad_ncdhw_tmp; } } else { input_ncdhw.ShareDataWith(input); out_grad_ncdhw.ShareDataWith(out_grad); if (input_grad) input_grad_ncdhw_ptr = input_grad; } if (filter_grad) { if (channel_last) dev_ctx.template Alloc(filter_grad); filter_grad_ncdhw_ptr = filter_grad; } const int64_t batch_size = input_ncdhw.dims()[0]; const int64_t in_channels = input_ncdhw.dims()[1]; const int64_t in_depth = input_ncdhw.dims()[2]; const int64_t in_height = input_ncdhw.dims()[3]; const int64_t in_width = input_ncdhw.dims()[4]; const int64_t out_channels = out_grad_ncdhw.dims()[1]; const int64_t out_depth = out_grad_ncdhw.dims()[2]; const int64_t out_height = out_grad_ncdhw.dims()[3]; const int64_t out_width = out_grad_ncdhw.dims()[4]; const int64_t kernel_t = filter_ncdhw.dims()[2]; const int64_t kernel_h = filter_ncdhw.dims()[3]; const int64_t kernel_w = filter_ncdhw.dims()[4]; std::vector paddings_vec; if (paddings.size() == 6) { paddings_vec = {paddings[0], paddings[2], paddings[4]}; } else if (paddings.size() == 3) { paddings_vec = paddings; } else { paddings_vec = {paddings[0], paddings[0], paddings[0]}; } auto stream = dev_ctx.stream(); using AccT = typename MPTypeTrait::Type; PADDLE_ENFORCE_LE_INT_MAX(batch_size, "batch_size"); PADDLE_ENFORCE_LE_INT_MAX(in_channels, "in_channels"); PADDLE_ENFORCE_LE_INT_MAX(in_depth, "in_depth"); PADDLE_ENFORCE_LE_INT_MAX(in_height, "in_height"); PADDLE_ENFORCE_LE_INT_MAX(in_width, "in_width"); PADDLE_ENFORCE_LE_INT_MAX(out_channels, "out_channels"); PADDLE_ENFORCE_LE_INT_MAX(out_depth, "out_depth"); PADDLE_ENFORCE_LE_INT_MAX(out_height, "out_height"); PADDLE_ENFORCE_LE_INT_MAX(out_width, "out_width"); PADDLE_ENFORCE_LE_INT_MAX(kernel_t, "kernel_t"); PADDLE_ENFORCE_LE_INT_MAX(kernel_h, "kernel_h"); PADDLE_ENFORCE_LE_INT_MAX(kernel_w, "kernel_w"); const int batch_size_int = static_cast(batch_size); const int in_channels_int = static_cast(in_channels); const int in_depth_int = static_cast(in_depth); const int in_height_int = static_cast(in_height); const int in_width_int = static_cast(in_width); const int out_channels_int = static_cast(out_channels); const int out_depth_int = static_cast(out_depth); const int out_height_int = static_cast(out_height); const int out_width_int = static_cast(out_width); const int kernel_t_int = static_cast(kernel_t); const int kernel_h_int = static_cast(kernel_h); const int kernel_w_int = static_cast(kernel_w); const T* input_ptr = input_ncdhw.data(); const T* grad_output_ptr = out_grad_ncdhw.data(); const T* filter_ptr = filter_ncdhw.data(); T* grad_input_ptr = input_grad_ncdhw_ptr ? input_grad_ncdhw_ptr->data() : nullptr; T* grad_filter_ptr = filter_grad_ncdhw_ptr ? filter_grad_ncdhw_ptr->data() : nullptr; // Input Gradient if (grad_input_ptr) { int64_t num_elements = input_grad_ncdhw_ptr->numel(); int block = 256; const int64_t grid_num = std::min((num_elements - 1) / block + 1, static_cast(65536)); dim3 grid(static_cast(grid_num)); dim3 block_dim(block); bool is_k3 = (kernel_t == 3 && kernel_h == 3 && kernel_w == 3); bool is_d1 = (dilations[0] == 1 && dilations[1] == 1 && dilations[2] == 1); bool is_s1 = (strides[0] == 1 && strides[1] == 1 && strides[2] == 1); if (is_k3 && is_d1 && is_s1) { DWConv3dBwdInputKernel <<>>(grad_output_ptr, grad_input_ptr, filter_ptr, batch_size_int, in_channels_int, in_depth_int, in_height_int, in_width_int, out_channels_int, out_depth_int, out_height_int, out_width_int, kernel_t_int, kernel_h_int, kernel_w_int, strides[0], strides[1], strides[2], paddings_vec[0], paddings_vec[1], paddings_vec[2], dilations[0], dilations[1], dilations[2]); } else if (is_k3 && is_d1) { DWConv3dBwdInputKernel <<>>(grad_output_ptr, grad_input_ptr, filter_ptr, batch_size_int, in_channels_int, in_depth_int, in_height_int, in_width_int, out_channels_int, out_depth_int, out_height_int, out_width_int, kernel_t_int, kernel_h_int, kernel_w_int, strides[0], strides[1], strides[2], paddings_vec[0], paddings_vec[1], paddings_vec[2], dilations[0], dilations[1], dilations[2]); } else if (is_k3 && is_s1) { DWConv3dBwdInputKernel <<>>(grad_output_ptr, grad_input_ptr, filter_ptr, batch_size_int, in_channels_int, in_depth_int, in_height_int, in_width_int, out_channels_int, out_depth_int, out_height_int, out_width_int, kernel_t_int, kernel_h_int, kernel_w_int, strides[0], strides[1], strides[2], paddings_vec[0], paddings_vec[1], paddings_vec[2], dilations[0], dilations[1], dilations[2]); } else if (is_k3) { DWConv3dBwdInputKernel <<>>(grad_output_ptr, grad_input_ptr, filter_ptr, batch_size_int, in_channels_int, in_depth_int, in_height_int, in_width_int, out_channels_int, out_depth_int, out_height_int, out_width_int, kernel_t_int, kernel_h_int, kernel_w_int, strides[0], strides[1], strides[2], paddings_vec[0], paddings_vec[1], paddings_vec[2], dilations[0], dilations[1], dilations[2]); } else { DWConv3dBwdInputKernel <<>>(grad_output_ptr, grad_input_ptr, filter_ptr, batch_size_int, in_channels_int, in_depth_int, in_height_int, in_width_int, out_channels_int, out_depth_int, out_height_int, out_width_int, kernel_t_int, kernel_h_int, kernel_w_int, strides[0], strides[1], strides[2], paddings_vec[0], paddings_vec[1], paddings_vec[2], dilations[0], dilations[1], dilations[2]); } } // Weight Gradient if (grad_filter_ptr) { int64_t num_weights = filter_grad_ncdhw_ptr->numel(); int block = 256; PADDLE_ENFORCE_LE_UINT32_MAX(num_weights, "num_weights"); PADDLE_ENFORCE_LE( num_weights, static_cast(dev_ctx.GetCUDAMaxGridDimSize()[0]), common::errors::InvalidArgument( "depthwise conv3d bias grad grid.x exceeds device limit.")); dim3 grid(static_cast(num_weights)); dim3 block_dim(block); size_t smem = block * sizeof(T); if (strides[1] == 1 && strides[2] == 1) { DWConv3dBwdWeightKernel <<>>(grad_output_ptr, input_ptr, grad_filter_ptr, batch_size_int, in_channels_int, in_depth_int, in_height_int, in_width_int, out_channels_int, out_depth_int, out_height_int, out_width_int, kernel_t_int, kernel_h_int, kernel_w_int, strides[0], strides[1], strides[2], paddings_vec[0], paddings_vec[1], paddings_vec[2], dilations[0], dilations[1], dilations[2]); } else if (strides[1] == 2 && strides[2] == 2) { DWConv3dBwdWeightKernel <<>>(grad_output_ptr, input_ptr, grad_filter_ptr, batch_size_int, in_channels_int, in_depth_int, in_height_int, in_width_int, out_channels_int, out_depth_int, out_height_int, out_width_int, kernel_t_int, kernel_h_int, kernel_w_int, strides[0], strides[1], strides[2], paddings_vec[0], paddings_vec[1], paddings_vec[2], dilations[0], dilations[1], dilations[2]); } else { DWConv3dBwdWeightKernel <<>>(grad_output_ptr, input_ptr, grad_filter_ptr, batch_size_int, in_channels_int, in_depth_int, in_height_int, in_width_int, out_channels_int, out_depth_int, out_height_int, out_width_int, kernel_t_int, kernel_h_int, kernel_w_int, strides[0], strides[1], strides[2], paddings_vec[0], paddings_vec[1], paddings_vec[2], dilations[0], dilations[1], dilations[2]); } } // Bias Gradient if (bias_grad) { dev_ctx.template Alloc(bias_grad); // Reduce N(0), D(2), H(3), W(4) -> C(1) for NCDHW std::vector reduce_dims = {0, 2, 3, 4}; SumKernel(dev_ctx, out_grad_ncdhw, IntArray(reduce_dims), CppTypeToDataType::Type(), false, bias_grad); } if (input_grad && channel_last) { TransToChannelLast(dev_ctx, input_grad_ncdhw_ptr, input_grad); } } template void DepthwiseConv3dBiasGradKernel(const Context& dev_ctx, const DenseTensor& input, const DenseTensor& filter, const optional& bias, const DenseTensor& out_grad, const std::vector& strides_t, const std::vector& paddings_t, const std::string& padding_algorithm, int groups, const std::vector& dilations_t, const std::string& data_format, DenseTensor* input_grad, DenseTensor* filter_grad, DenseTensor* bias_grad) { if (!input_grad && !filter_grad && !bias_grad) return; // 0-size Check if (input.numel() == 0) { if (input_grad) dev_ctx.template Alloc(input_grad); if (filter_grad) { dev_ctx.template Alloc(filter_grad); Full(dev_ctx, filter_grad->dims(), 0, filter_grad); } if (bias_grad) { dev_ctx.template Alloc(bias_grad); Full(dev_ctx, bias_grad->dims(), 0, bias_grad); } return; } // Alloc if (input_grad) dev_ctx.template Alloc(input_grad); if (filter_grad) dev_ctx.template Alloc(filter_grad); std::vector strides = strides_t; std::vector dilations = dilations_t; std::vector paddings = paddings_t; // Update Padding auto in_dims = input.dims(); auto filter_dims = filter.dims(); DDim in_data_dims; const DataLayout data_layout = StringToDataLayout(data_format); if (data_layout != DataLayout::NDHWC) { in_data_dims = slice_ddim(in_dims, 2, in_dims.size()); } else { in_data_dims = slice_ddim(in_dims, 1, in_dims.size() - 1); } DDim filter_data_dims = slice_ddim(filter_dims, 2, filter_dims.size()); std::vector ksize = vectorize(filter_data_dims); UpdatePaddingAndDilation( &paddings, &dilations, padding_algorithm, in_data_dims, strides, ksize); bool is_sys_pad = strides.size() * 2 == paddings.size() ? false : true; if (!is_sys_pad) { for (size_t i = 0; i < strides.size(); ++i) { paddings.erase(paddings.begin() + i + 1); } } LaunchDepthwiseConv3dBackwardCompatible(dev_ctx, input, filter, out_grad, strides, paddings, dilations, data_format, input_grad, filter_grad, bias_grad); } } // namespace phi PD_REGISTER_KERNEL(depthwise_conv3d_bias_grad, GPU, ALL_LAYOUT, phi::DepthwiseConv3dBiasGradKernel, float, double, phi::float16, phi::bfloat16) {}