Files
2026-07-13 12:40:42 +08:00

284 lines
10 KiB
C++

// 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/batch_norm_grad_kernel.h"
#include "paddle/phi/kernels/full_kernel.h"
#include "paddle/phi/backends/xpu/enforce_xpu.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/norm_utils.h"
namespace phi {
template <typename T>
static int CalculateInvBNY(xpu::Context *xpu_ctx,
T *x,
const T *scale,
const T *bias,
const T *mean,
const T *variance,
const int64_t N,
const int64_t C,
const int64_t M,
const T *y) {
PADDLE_ENFORCE_EQ(x,
y,
common::errors::InvalidArgument(
"X and Y should be inplaced in inplace mode"));
std::vector<int64_t> tensor_shape_vec({N, C, M});
std::vector<int64_t> array_shape_vec({1, C, 1});
// y - bias
int r1 = xpu::broadcast_sub<T>(
xpu_ctx, bias, y, x, array_shape_vec, tensor_shape_vec);
// (y - bias) / scale
int r2 = xpu::broadcast_div<T>(
xpu_ctx, scale, x, x, array_shape_vec, tensor_shape_vec);
// (y - bias) / scale / variance
int r3 = xpu::broadcast_div<T>(
xpu_ctx, variance, x, x, array_shape_vec, tensor_shape_vec);
// (y - bias) / scale / variance + mean
int r4 = xpu::broadcast_add<T>(
xpu_ctx, mean, x, x, array_shape_vec, tensor_shape_vec);
return r1 + r2 + r3 + r4;
}
template <typename T>
static int CalculateInvVar(xpu::Context *xpu_ctx,
const T *var,
const T epsilon,
const int64_t C,
T *epsilon_data,
T *inv_var) {
int r1 = constant(xpu_ctx, epsilon_data, 1, epsilon);
std::vector<int64_t> tensor_shape_vec({C});
std::vector<int64_t> array_shape_vec({1});
int r2 = xpu::broadcast_add<T>(
xpu_ctx, epsilon_data, var, inv_var, array_shape_vec, tensor_shape_vec);
int r3 = xpu::rsqrt<T>(xpu_ctx, inv_var, inv_var, C);
return r1 + r2 + r3;
}
template <typename T, typename Context>
void BatchNormGradKernel(const Context &dev_ctx,
const DenseTensor &x,
const optional<DenseTensor> &scale,
const optional<DenseTensor> &bias,
const optional<DenseTensor> &mean,
const optional<DenseTensor> &variance,
const DenseTensor &saved_mean,
const DenseTensor &saved_variance,
const optional<DenseTensor> &reserve_space,
const DenseTensor &y_grad,
float momentum,
float epsilon,
const std::string &data_layout,
bool is_test,
bool use_global_stats,
bool trainable_statistics,
DenseTensor *x_grad,
DenseTensor *scale_grad,
DenseTensor *bias_grad) {
if (x.numel() == 0) {
dev_ctx.template Alloc<T>(x_grad);
if (scale_grad)
Full<T, Context>(dev_ctx, scale_grad->dims(), 0, scale_grad);
if (bias_grad) Full<T, Context>(dev_ctx, bias_grad->dims(), 0, bias_grad);
return;
}
using XPUType = typename XPUTypeTrait<T>::Type;
const auto *d_y = &y_grad;
PADDLE_ENFORCE_EQ(data_layout == "NCHW" || data_layout == "NHWC",
true,
common::errors::InvalidArgument(
"The 'data_layout' attribute must be NCHW or NHWC. "
"But received 'data_layout' is [%s].",
data_layout));
const auto data_layout_val = StringToDataLayout(data_layout);
use_global_stats = is_test || use_global_stats;
// batch_norm with inplace as false will take X as grad input, which
// is same as cuDNN batch_norm backward calculation, batch_norm
// with inplace as true only take Y as input and X should be calculate
// by inverse operation of batch_norm on Y
bool is_inplace = false;
if (x_grad) {
PADDLE_ENFORCE_NE(x_grad,
d_y,
common::errors::InvalidArgument(
"X@GRAD and Y@GRAD inplaced in non-inplace mode"));
}
const auto &x_dims = x.dims();
PADDLE_ENFORCE_EQ(
x_dims.size() >= 2 && x_dims.size() <= 5,
true,
common::errors::InvalidArgument(
"The size of input's dimensions should be between 2 and 5. "
"But received: the size of input's dimensions is [%d]",
x_dims.size()));
int64_t N = -1, C = -1, H = -1, W = -1, D = -1;
funcs::ExtractNCWHD(x_dims, data_layout_val, &N, &C, &H, &W, &D);
N = (N == 0) ? 1 : N;
C = (C == 0) ? 1 : C;
H = (H == 0) ? 1 : H;
W = (W == 0) ? 1 : W;
D = (D == 0) ? 1 : D;
W = W * D;
auto *Scale = scale.get_ptr();
auto *Bias = bias.get_ptr();
DenseTensor new_scale;
DenseTensor new_bias;
if (Scale) {
new_scale = scale.get();
} else {
new_scale = Full<T, Context>(dev_ctx, {C}, static_cast<T>(1));
}
if (Bias) {
new_bias = bias.get();
} else {
new_bias = Full<T, Context>(dev_ctx, {C}, static_cast<T>(0));
}
const auto *x_data = reinterpret_cast<const XPUType *>(x.data<T>());
const auto *d_y_data = reinterpret_cast<const XPUType *>(y_grad.data<T>());
const auto *scale_data = new_scale.data<float>();
// init output
XPUType *x_grad_data = nullptr;
float *bias_grad_data = nullptr;
float *scale_grad_data = nullptr;
if (x_grad) {
x_grad_data =
reinterpret_cast<XPUType *>(dev_ctx.template Alloc<T>(x_grad));
}
if (scale_grad && bias_grad) {
scale_grad_data = dev_ctx.template Alloc<float>(scale_grad);
bias_grad_data = dev_ctx.template Alloc<float>(bias_grad);
}
PADDLE_ENFORCE_EQ(
new_scale.dims().size(),
1UL,
common::errors::InvalidArgument(
"The size of scale's dimensions must equal to 1. But received: "
"the size of scale's dimensions is [%d], the dimensions of scale "
"is [%s].",
new_scale.dims().size(),
new_scale.dims()));
PADDLE_ENFORCE_EQ(
new_scale.dims()[0],
C,
common::errors::InvalidArgument(
"The first dimension of scale must equal to Channels[%d]. But "
"received: the first dimension of scale is [%d]",
C,
new_scale.dims()[0]));
xpu::ctx_guard RAII_GUARD(dev_ctx.x_context());
const auto *global_mean = mean.get_ptr();
const auto *global_var = variance.get_ptr();
float *global_inv_std_data = nullptr;
int r = 0;
if (use_global_stats) {
PADDLE_ENFORCE_NOT_NULL(
global_mean,
errors::InvalidArgument(
"global_mean cannot be nullptr when use_global_stats is True"));
PADDLE_ENFORCE_NOT_NULL(
global_var,
errors::InvalidArgument(
"global_var cannot be nullptr when use_global_stats is True"));
global_inv_std_data = RAII_GUARD.alloc_l3_or_gm<float>(global_var->numel());
float *epsilon_data = RAII_GUARD.alloc_l3_or_gm<float>(1);
r = CalculateInvVar(dev_ctx.x_context(),
global_var->data<float>(),
epsilon,
C,
epsilon_data,
global_inv_std_data);
PADDLE_ENFORCE_XDNN_SUCCESS(r, "batch_norm_grad CalculateInvVar function");
}
auto *inv_std_data =
use_global_stats ? global_inv_std_data : saved_variance.data<float>();
auto *mean_data =
use_global_stats ? global_mean->data<float>() : saved_mean.data<float>();
// TODO(guozibin): handle the situation case of N * H * W = 1
if (is_inplace) {
// Here is a trick, x is a const input,
// but trans to a non-const var, is it risky?
float *x_fp32_data = RAII_GUARD.alloc_l3_or_gm<float>(x.numel());
r = xpu::cast<XPUType, float>(
dev_ctx.x_context(), x_data, x_fp32_data, x.numel());
PADDLE_ENFORCE_XDNN_SUCCESS(r, "cast");
r = CalculateInvBNY(dev_ctx.x_context(),
x_fp32_data,
new_scale.data<float>(),
new_bias.data<float>(),
mean_data,
inv_std_data,
N,
C,
H * W,
x_fp32_data);
PADDLE_ENFORCE_XDNN_SUCCESS(r, "batch_norm_grad CalculateInvBNY function");
}
bool is_nchw = data_layout == "NCHW";
if (!x_grad) {
x_grad_data = RAII_GUARD.alloc_l3_or_gm<XPUType>(x.numel());
}
if (!scale_grad) {
scale_grad_data = RAII_GUARD.alloc_l3_or_gm<float>(C);
}
if (!bias_grad_data) {
bias_grad_data = RAII_GUARD.alloc_l3_or_gm<float>(C);
}
r = xpu::batch_norm_grad<XPUType>(dev_ctx.x_context(),
x_data,
d_y_data,
x_grad_data,
N,
C,
H,
W,
scale_data,
mean_data,
inv_std_data,
scale_grad_data,
bias_grad_data,
is_nchw);
PADDLE_ENFORCE_XDNN_SUCCESS(r, "batch_norm_grad");
}
} // namespace phi
PD_REGISTER_KERNEL(batch_norm_grad,
XPU,
ALL_LAYOUT,
phi::BatchNormGradKernel,
float,
phi::float16) {}