// Copyright (c) 2026 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 #include #include #include #include #include #include "paddle/common/flags.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/core/dense_tensor.h" #include "paddle/phi/kernels/concat_kernel.h" #include "paddle/phi/kernels/contiguous_kernel.h" #include "paddle/phi/kernels/conv_kernel.h" #include "paddle/phi/kernels/cpu/conv_util.h" #include "paddle/phi/kernels/elementwise_add_kernel.h" #include "paddle/phi/kernels/fill_kernel.h" #include "paddle/phi/kernels/full_kernel.h" #include "paddle/phi/kernels/funcs/batch_norm_utils.h" #include "paddle/phi/kernels/funcs/blas/blas.h" #include "paddle/phi/kernels/funcs/im2col_slow.cuh" #include "paddle/phi/kernels/funcs/math_function.h" #include "paddle/phi/kernels/funcs/vol2col_slow.cuh" #include "paddle/phi/kernels/reduce_sum_kernel.h" #include "paddle/phi/kernels/slice_kernel.h" COMMON_DECLARE_bool(use_accuracy_compatible_kernel); namespace phi { template static inline T div_rtn(T x, T y) { int q = x / y; int r = x % y; if ((r != 0) && ((r < 0) != (y < 0))) --q; return q; } template , int> = 0> inline int64_t multiply_integers(const C& container) { return std::accumulate(container.begin(), container.end(), static_cast(1), std::multiplies<>()); } template ::value_type>, int> = 0> inline int64_t multiply_integers(Iter begin, Iter end) { return std::accumulate( begin, end, static_cast(1), std::multiplies<>()); } template std::vector GetOutputSpatialSize( const DenseTensor& input, const std::vector& kernel_size, const std::vector& stride_size, const std::vector& pad_size, const std::vector& dilation_size) { std::vector sizes; auto input_dim = input.dims().size(); for (int64_t index = 0; index < dim; ++index) { int64_t input_size = input.dims()[index + input_dim - dim]; int64_t kernel = kernel_size[index]; int64_t stride = stride_size[index]; int64_t pad = pad_size[index]; int64_t dilation = dilation_size[index]; int64_t numerator = input_size + 2 * pad - (dilation * (kernel - 1) + 1); int64_t size = div_rtn(numerator, stride) + 1; sizes.push_back(size); } return sizes; } template std::vector GetOutputSize(const DenseTensor& input, const DenseTensor& weight, const std::vector& kernel_size, const std::vector& stride_size, const std::vector& pad_size, const std::vector& dilation_size) { auto output_size = GetOutputSpatialSize( input, kernel_size, stride_size, pad_size, dilation_size); output_size.insert(output_size.begin(), weight.dims()[0]); if (input.dims().size() == dim + 2) { output_size.insert(output_size.begin(), input.dims()[0]); } return output_size; } template void hvol2col(const Context& dev_ctx, const T* data_hvol, int channels, const std::vector& input_size, const std::vector& output_size, const std::vector& kernel_size, const std::vector& stride_size, const std::vector& pad_size, const std::vector& dilation_size, T* data_col) { if (dim == 3) { funcs::vol2col_slow(dev_ctx, data_hvol, channels, input_size[0], input_size[1], input_size[2], output_size[0], output_size[1], output_size[2], kernel_size[0], kernel_size[1], kernel_size[2], pad_size[0], pad_size[1], pad_size[2], stride_size[0], stride_size[1], stride_size[2], dilation_size[0], dilation_size[1], dilation_size[2], data_col); } else if (dim == 2) { funcs::im2col_slow(dev_ctx, data_hvol, channels, input_size[0], input_size[1], output_size[0], output_size[1], kernel_size[0], kernel_size[1], pad_size[0], pad_size[1], stride_size[0], stride_size[1], dilation_size[0], dilation_size[1], data_col); } } template void col2hvol(const Context& dev_ctx, const T* data_col, const int channels, const std::vector& input_size, const std::vector& output_size, const std::vector& kernel_size, const std::vector& stride_size, const std::vector& pad_size, const std::vector& dilation_size, T* data_hvol) { if (dim == 3) { funcs::col2vol_slow(dev_ctx, data_col, channels, input_size[0], input_size[1], input_size[2], output_size[0], output_size[1], output_size[2], kernel_size[0], kernel_size[1], kernel_size[2], pad_size[0], pad_size[1], pad_size[2], stride_size[0], stride_size[1], stride_size[2], dilation_size[0], dilation_size[1], dilation_size[2], data_hvol); } if (dim == 2) { funcs::col2im_slow(dev_ctx, data_col, channels, input_size[0], input_size[1], output_size[0], output_size[1], kernel_size[0], kernel_size[1], pad_size[0], pad_size[1], stride_size[0], stride_size[1], dilation_size[0], dilation_size[1], data_hvol); } } // Select View function template DenseTensor Select(const DenseTensor& src, int64_t index) { DenseTensor out; out.ShareDataWith(src); auto dims = src.dims(); std::vector new_dims; for (int i = 1; i < dims.size(); ++i) { new_dims.push_back(dims[i]); } out.Resize(new_dims); int64_t stride_0 = src.numel() / dims[0]; size_t offset_bytes = index * stride_0 * sizeof(T); out.set_offset(src.offset() + offset_bytes); return out; } template void SlowConvDilatedAllCUDAImpl(const Context& dev_ctx, DenseTensor* output, const DenseTensor* input, const DenseTensor* weight, const DenseTensor* bias, const DenseTensor* grad_output, DenseTensor* grad_input, DenseTensor* grad_weight, DenseTensor* grad_bias, const std::vector& kernel_size, const std::vector& strides, const std::vector& paddings, const std::vector& dilations) { const int64_t batch_size = input->dims()[0]; const int64_t input_channels = weight->dims()[1]; const int64_t output_channels = weight->dims()[0]; std::vector input_spatial_size; for (int i = 2; i < input->dims().size(); ++i) { input_spatial_size.push_back(input->dims()[i]); } std::vector output_spatial_size = GetOutputSpatialSize( *input, kernel_size, strides, paddings, dilations); int64_t kernel_volume = multiply_integers(kernel_size); int64_t output_volume = multiply_integers(output_spatial_size); // Buffer int64_t col_dim0 = input_channels * kernel_volume; int64_t col_dim1 = output_volume; DenseTensor columns; if (output || grad_weight || grad_input) { columns.Resize({col_dim0, col_dim1}); dev_ctx.template Alloc(&columns); } // Initialize funcs::SetConstant set_zero; if (grad_weight) set_zero(dev_ctx, grad_weight, static_cast(0)); if (grad_bias) set_zero(dev_ctx, grad_bias, static_cast(0)); if (output && !bias) set_zero(dev_ctx, output, static_cast(0)); // Bias CPU Mirror DenseTensor bias_cpu; const T* bias_cpu_data = nullptr; if (output && bias) { Copy(dev_ctx, *bias, CPUPlace(), true, &bias_cpu); bias_cpu_data = bias_cpu.data(); } DenseTensor grad_output_n; std::vector sum_axes; for (int i = 0; i < Dims; ++i) sum_axes.push_back(i + 1); auto blas = funcs::GetBlas(dev_ctx); for (int elt = 0; elt < batch_size; ++elt) { T* columns_ptr = columns.data(); // Prepare Input Slice View DenseTensor input_n = Select(*input, elt); const T* input_ptr_raw = input_n.data(); // Forward if (output) { DenseTensor output_n = Select(*output, elt); T* output_ptr_raw = output_n.data(); if (bias) { for (int n = 0; n < output_channels; ++n) { DenseTensor out_slice = Select(output_n, n); FillKernel( dev_ctx, out_slice, Scalar(bias_cpu_data[n]), &out_slice); } } hvol2col(dev_ctx, input_ptr_raw, input_channels, input_spatial_size, output_spatial_size, kernel_size, strides, paddings, dilations, columns_ptr); blas.GEMM(false, // TransA false, // TransB static_cast(output_channels), // M static_cast(col_dim1), // N static_cast(col_dim0), // K static_cast(1), // alpha weight->data(), // A static_cast(col_dim0), // lda columns_ptr, // B static_cast(col_dim1), // ldb static_cast(1), // beta = 1 (Accumulate) output_ptr_raw, // C static_cast(col_dim1) // ldc ); } else { grad_output_n = Select(*grad_output, elt); } // Backward Grad Input if (grad_input) { DenseTensor grad_input_n = Select(*grad_input, elt); T* grad_input_ptr_raw = grad_input_n.data(); const T* grad_output_ptr_raw = grad_output_n.data(); blas.GEMM(true, // TransA false, // TransB static_cast(col_dim0), // M static_cast(col_dim1), // N static_cast(output_channels), // K static_cast(1), // alpha weight->data(), // A static_cast(col_dim0), // lda grad_output_ptr_raw, // B static_cast(col_dim1), // ldb static_cast(0), // beta columns_ptr, // C static_cast(col_dim1) // ldc ); col2hvol(dev_ctx, columns_ptr, input_channels, input_spatial_size, output_spatial_size, kernel_size, strides, paddings, dilations, grad_input_ptr_raw); } // Backward Grad Weight if (grad_weight) { const T* grad_output_ptr_raw = grad_output_n.data(); hvol2col(dev_ctx, input_ptr_raw, input_channels, input_spatial_size, output_spatial_size, kernel_size, strides, paddings, dilations, columns_ptr); blas.GEMM(false, // TransA true, // TransB static_cast(output_channels), // M static_cast(col_dim0), // N static_cast(col_dim1), // K static_cast(1), // alpha grad_output_ptr_raw, // A static_cast(col_dim1), // lda columns_ptr, // B static_cast(col_dim1), // ldb static_cast(1), // beta grad_weight->data(), // C static_cast(col_dim0) // ldc ); } // Backward Grad Bias if (grad_bias) { DenseTensor sum_result = Sum(dev_ctx, grad_output_n, IntArray(sum_axes), CppTypeToDataType::Type(), false); Add(dev_ctx, *grad_bias, sum_result, grad_bias); } } } template void SlowConvBackwardNoGroup(const Context& dev_ctx, const DenseTensor& grad_output, const DenseTensor& input, const DenseTensor& weight, const std::vector& kernel_size, const std::vector& strides, const std::vector& paddings, const std::vector& dilations, DenseTensor* grad_input, DenseTensor* grad_weight, DenseTensor* grad_bias) { int64_t rank = input.dims().size(); bool is_batch = (rank == (dim + 2)); // tensor.unsqueeze(0) auto make_batch_view = [&](const DenseTensor& src, DenseTensor& dst) { if (!is_batch) { dst.ShareDataWith(src); std::vector new_shape = {1}; for (int i = 0; i < src.dims().size(); ++i) new_shape.push_back(src.dims()[i]); dst.Resize(new_shape); } else { dst.ShareDataWith(src); } }; DenseTensor grad_output_; make_batch_view(grad_output, grad_output_); DenseTensor input_; make_batch_view(input, input_); const DenseTensor& weight_ = weight; DenseTensor grad_input_view; DenseTensor* grad_input_ptr = nullptr; if (grad_input) { dev_ctx.template Alloc(grad_input); if (!is_batch) { grad_input_view.ShareDataWith(*grad_input); std::vector new_shape = {1}; for (int i = 0; i < grad_input->dims().size(); ++i) new_shape.push_back(grad_input->dims()[i]); grad_input_view.Resize(new_shape); grad_input_ptr = &grad_input_view; } else { grad_input_ptr = grad_input; } } DenseTensor* grad_weight_ptr = nullptr; if (grad_weight) { dev_ctx.template Alloc(grad_weight); grad_weight_ptr = grad_weight; } DenseTensor* grad_bias_ptr = nullptr; if (grad_bias) { dev_ctx.template Alloc(grad_bias); grad_bias_ptr = grad_bias; } SlowConvDilatedAllCUDAImpl( dev_ctx, nullptr, // [Output] &input_, // [Input] &weight_, // [Weight] nullptr, // [Bias] &grad_output_, // [GradOutput] grad_input_ptr, // [GradInput] grad_weight_ptr, // [GradWeight] grad_bias_ptr, // [GradBias] (New) kernel_size, strides, paddings, dilations); } template void SlowConvNoGroup(const Context& dev_ctx, const DenseTensor& input, const DenseTensor& weight, const DenseTensor* bias, const std::vector& kernel_size, const std::vector& strides, const std::vector& paddings, const std::vector& dilations, DenseTensor* output) { int64_t rank = input.dims().size(); bool is_batch = (rank == (dim + 2)); // (is_batch ? input.contiguous() : input.contiguous().unsqueeze(0)); DenseTensor input_; if (!is_batch) { input_.ShareDataWith(input); std::vector new_shape = {1}; for (int i = 0; i < rank; ++i) new_shape.push_back(input.dims()[i]); input_.Resize(new_shape); } else { input_.ShareDataWith(input); } const DenseTensor& weight_ = weight; // (is_batch ? output : output.unsqueeze(0)); if (output) dev_ctx.template Alloc(output); DenseTensor output_; if (!is_batch) { output_.ShareDataWith(*output); std::vector out_shape = {1}; for (int i = 0; i < output->dims().size(); ++i) { out_shape.push_back(output->dims()[i]); } output_.Resize(out_shape); } else { output_.ShareDataWith(*output); } SlowConvDilatedAllCUDAImpl(dev_ctx, &output_, // [Output] &input_, // [Input] &weight_, // [Weight] bias, // [Bias] nullptr, // [GradOutput] nullptr, // [GradInput] nullptr, // [GradWeight] nullptr, // [GradBias] kernel_size, strides, paddings, dilations); } template void SlowConvForward(const Context& dev_ctx, const DenseTensor& input, const DenseTensor& filter_t, const paddle::optional& bias, const std::vector& strides, const std::vector& paddings_t, const std::string& padding_algorithm, int groups, const std::vector& dilations_t, const std::string& data_format, DenseTensor* output) { std::vector paddings = paddings_t; std::vector dilations = dilations_t; DenseTensor filter = filter_t; if (input.numel() == 0 || filter.numel() == 0) { Full(dev_ctx, output->dims(), 0, output); return; } dev_ctx.template Alloc(output); const bool channel_last = (data_format == "NHWC" || data_format == "NDHWC"); DenseTensor transformed_input(input.type()); DenseTensor transformed_output(output->type()); if (channel_last) { ResizeToChannelFirst(dev_ctx, &input, &transformed_input); TransToChannelFirst(dev_ctx, &input, &transformed_input); ResizeToChannelFirst(dev_ctx, output, &transformed_output); } else { transformed_input = input; transformed_output = *output; } // update padding and dilation auto trans_in_dims = transformed_input.dims(); auto filter_dims = filter.dims(); DDim in_data_dims = slice_ddim(trans_in_dims, 2, trans_in_dims.size()); 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); // ================================================================= // Contiguous & Grouping // ================================================================= DenseTensor input_contiguous; ContiguousKernel(dev_ctx, transformed_input, &input_contiguous); DenseTensor weight_contiguous; ContiguousKernel(dev_ctx, filter_t, &weight_contiguous); auto to_int64_vec = [](const std::vector& in) { return std::vector(in.begin(), in.end()); }; const DenseTensor* bias_ptr = bias.get_ptr(); DenseTensor bias_contiguous; if (bias_ptr) { ContiguousKernel(dev_ctx, *bias_ptr, &bias_contiguous); bias_ptr = &bias_contiguous; } if (groups == 1) { SlowConvNoGroup(dev_ctx, input_contiguous, weight_contiguous, bias_ptr, to_int64_vec(ksize), to_int64_vec(strides), to_int64_vec(paddings), to_int64_vec(dilations), &transformed_output); } else { int64_t in_rank = input_contiguous.dims().size(); bool has_batch = (in_rank == dim + 2); int channel_dim = has_batch ? 1 : 0; int64_t in_channels = input_contiguous.dims()[channel_dim]; int64_t out_channels = weight_contiguous.dims()[0]; int64_t in_g_sz = in_channels / groups; int64_t out_g_sz = out_channels / groups; std::vector outputs(groups); for (int g = 0; g < groups; ++g) { // Slice Input (Channel) DenseTensor input_g; SliceKernel(dev_ctx, input_contiguous, {channel_dim}, {g * in_g_sz}, {(g + 1) * in_g_sz}, {1}, {}, &input_g); // Slice Weight (OutChannel dim 0) DenseTensor weight_g; SliceKernel(dev_ctx, weight_contiguous, {0}, {g * out_g_sz}, {(g + 1) * out_g_sz}, {1}, {}, &weight_g); // Slice Bias (OutChannel dim 0) DenseTensor bias_g; const DenseTensor* bias_g_ptr = nullptr; if (bias_ptr) { SliceKernel(dev_ctx, *bias_ptr, {0}, {g * out_g_sz}, {(g + 1) * out_g_sz}, {1}, {}, &bias_g); bias_g_ptr = &bias_g; } DenseTensor output_g; auto out_shape = transformed_output.dims(); out_shape[channel_dim] = out_g_sz; output_g.Resize(out_shape); dev_ctx.template Alloc(&output_g); SlowConvNoGroup(dev_ctx, input_g, weight_g, bias_g_ptr, to_int64_vec(ksize), to_int64_vec(strides), to_int64_vec(paddings), to_int64_vec(dilations), &output_g); outputs[g] = output_g; } // Concat std::vector outputs_ptr; for (auto& t : outputs) outputs_ptr.push_back(&t); ConcatKernel( dev_ctx, outputs_ptr, channel_dim, &transformed_output); } if (channel_last) { TransToChannelLast(dev_ctx, &transformed_output, output); } } template void SlowConvBackward(const Context& dev_ctx, const DenseTensor& input, const DenseTensor& filter_t, const DenseTensor& output_grad, const std::vector& strides, const std::vector& paddings_t, const std::string& padding_algorithm, const std::vector& dilations_t, int groups, const std::string& data_format, DenseTensor* input_grad, DenseTensor* filter_grad, DenseTensor* bias_grad) { if (!input_grad && !filter_grad && !bias_grad) return; std::vector paddings = paddings_t; std::vector dilations = dilations_t; DenseTensor filter = filter_t; // 0-size if (input.numel() == 0 || filter_t.numel() == 0) { if (input_grad) dev_ctx.template Alloc(input_grad); if (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; } if (input_grad) dev_ctx.template Alloc(input_grad); if (filter_grad) dev_ctx.template Alloc(filter_grad); if (bias_grad) dev_ctx.template Alloc(bias_grad); const bool channel_last = (data_format == "NHWC" || data_format == "NDHWC"); DenseTensor transformed_input(input.type()); DenseTensor transformed_output_grad(output_grad.type()); if (channel_last) { ResizeToChannelFirst(dev_ctx, &input, &transformed_input); TransToChannelFirst(dev_ctx, &input, &transformed_input); ResizeToChannelFirst( dev_ctx, &output_grad, &transformed_output_grad); TransToChannelFirst( dev_ctx, &output_grad, &transformed_output_grad); } else { transformed_input = input; transformed_output_grad = output_grad; } // update padding and dilation auto in_dims = transformed_input.dims(); auto filter_dims = filter.dims(); DDim in_data_dims = slice_ddim(in_dims, 2, in_dims.size()); 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); // ================================================================= // Contiguous & Grouping // ================================================================= DenseTensor tmp_input_grad; DenseTensor* t_input_grad_ptr = nullptr; DenseTensor* t_filter_grad_ptr = filter_grad; DenseTensor* t_bias_grad_ptr = bias_grad; if (input_grad) { if (channel_last) { tmp_input_grad.Resize(transformed_input.dims()); t_input_grad_ptr = &tmp_input_grad; } else { t_input_grad_ptr = input_grad; } } // Contiguous DenseTensor grad_output_cont; ContiguousKernel( dev_ctx, transformed_output_grad, &grad_output_cont); DenseTensor input_cont; ContiguousKernel(dev_ctx, transformed_input, &input_cont); DenseTensor weight_cont; ContiguousKernel(dev_ctx, filter, &weight_cont); auto to_int64_vec = [](const std::vector& in) { return std::vector(in.begin(), in.end()); }; // Group if (groups == 1) { SlowConvBackwardNoGroup(dev_ctx, grad_output_cont, input_cont, weight_cont, to_int64_vec(ksize), to_int64_vec(strides), to_int64_vec(paddings), to_int64_vec(dilations), t_input_grad_ptr, t_filter_grad_ptr, t_bias_grad_ptr); } else { int64_t in_rank = input_cont.dims().size(); bool has_batch = (in_rank == dim + 2); int channel_dim = has_batch ? 1 : 0; int64_t in_channels = input_cont.dims()[channel_dim]; int64_t out_channels = grad_output_cont.dims()[channel_dim]; int64_t in_g_sz = in_channels / groups; int64_t out_g_sz = out_channels / groups; std::vector grad_inputs_g(groups); std::vector grad_weights_g(groups); std::vector grad_biases_g(groups); for (int g = 0; g < groups; ++g) { // Slice GradOutput (Channel) DenseTensor grad_output_g; SliceKernel(dev_ctx, grad_output_cont, {channel_dim}, {g * out_g_sz}, {(g + 1) * out_g_sz}, {1}, {}, &grad_output_g); // Slice Input (Channel) DenseTensor input_g; SliceKernel(dev_ctx, input_cont, {channel_dim}, {g * in_g_sz}, {(g + 1) * in_g_sz}, {1}, {}, &input_g); // Slice Weight (Output Channel / dim 0) DenseTensor weight_g; SliceKernel(dev_ctx, weight_cont, {0}, {g * out_g_sz}, {(g + 1) * out_g_sz}, {1}, {}, &weight_g); DenseTensor grad_input_g_tensor; DenseTensor grad_weight_g_tensor; DenseTensor grad_bias_g_tensor; if (t_input_grad_ptr) { auto g_shape = t_input_grad_ptr->dims(); g_shape[channel_dim] = in_g_sz; grad_input_g_tensor.Resize(g_shape); } if (t_filter_grad_ptr) { auto w_shape = t_filter_grad_ptr->dims(); w_shape[0] = out_g_sz; grad_weight_g_tensor.Resize(w_shape); } if (t_bias_grad_ptr) { auto b_shape = t_bias_grad_ptr->dims(); b_shape[0] = out_g_sz; grad_bias_g_tensor.Resize(b_shape); } SlowConvBackwardNoGroup( dev_ctx, grad_output_g, input_g, weight_g, to_int64_vec(ksize), to_int64_vec(strides), to_int64_vec(paddings), to_int64_vec(dilations), (t_input_grad_ptr ? &grad_input_g_tensor : nullptr), (t_filter_grad_ptr ? &grad_weight_g_tensor : nullptr), (t_bias_grad_ptr ? &grad_bias_g_tensor : nullptr)); if (t_input_grad_ptr) grad_inputs_g[g] = grad_input_g_tensor; if (t_filter_grad_ptr) grad_weights_g[g] = grad_weight_g_tensor; if (t_bias_grad_ptr) grad_biases_g[g] = grad_bias_g_tensor; } // Concat Input Grad if (t_input_grad_ptr) { std::vector ptrs; for (auto& t : grad_inputs_g) ptrs.push_back(&t); ConcatKernel(dev_ctx, ptrs, channel_dim, t_input_grad_ptr); } // Concat Weight Grad if (t_filter_grad_ptr) { std::vector ptrs; for (auto& t : grad_weights_g) ptrs.push_back(&t); ConcatKernel(dev_ctx, ptrs, 0, t_filter_grad_ptr); } // Concat Bias Grad if (t_bias_grad_ptr) { std::vector ptrs; for (auto& t : grad_biases_g) ptrs.push_back(&t); ConcatKernel(dev_ctx, ptrs, 0, t_bias_grad_ptr); } } if (channel_last && input_grad) { TransToChannelLast(dev_ctx, t_input_grad_ptr, input_grad); } } } // namespace phi