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

509 lines
18 KiB
Plaintext

// 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/cumprod_grad_kernel.h"
#include <thrust/transform.h>
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/complex_functors.h"
#include "paddle/phi/kernels/funcs/cumprod.h"
#include "paddle/phi/kernels/funcs/elementwise_functor.h"
#include "paddle/phi/kernels/funcs/exclusive_scan.h"
#include "paddle/phi/kernels/funcs/for_range.h"
#include "paddle/phi/kernels/funcs/inclusive_scan.h"
// NOTE(@xiongkun): use of IsComplex<>
#include "paddle/phi/core/utils/data_type.h"
#include "paddle/phi/kernels/compare_kernel.h"
#include "paddle/phi/kernels/complex_kernel.h"
#include "paddle/phi/kernels/cum_kernel.h"
#include "paddle/phi/kernels/elementwise_divide_kernel.h"
#include "paddle/phi/kernels/elementwise_multiply_kernel.h"
#include "paddle/phi/kernels/flip_kernel.h"
#include "paddle/phi/kernels/full_kernel.h"
#include "paddle/phi/kernels/reduce_any_kernel.h"
#include "paddle/common/flags.h"
COMMON_DECLARE_bool(use_accuracy_compatible_kernel);
namespace phi {
template <typename T>
struct CumprodGradFunctorExceptFirstZero {
HOSTDEVICE CumprodGradFunctorExceptFirstZero(
const T *x,
const T *y,
const T *dy_mul_y_reversed_cumsum,
const uint8_t *zero_mask,
size_t mid_dim,
size_t inner_dim,
bool exclusive,
bool reverse,
T *dx,
int64_t *first_zero_idx,
T *x_filled_one)
: x_(x),
y_(y),
dy_mul_y_reversed_cumsum_(dy_mul_y_reversed_cumsum),
zero_mask_(zero_mask),
mid_dim_(mid_dim),
inner_dim_(inner_dim),
exclusive_(exclusive),
reverse_(reverse),
dx_(dx),
first_zero_idx_(first_zero_idx),
x_filled_one_(x_filled_one) {}
HOSTDEVICE void operator()(size_t idx) const {
auto inner_idx = idx % inner_dim_;
auto outer_idx = idx / (mid_dim_ * inner_dim_);
auto mid_idx = (idx - inner_idx) / inner_dim_ % mid_dim_;
auto mask = zero_mask_[idx];
bool should_fill_one = true;
size_t final_index = reverse_ ? 0 : mid_dim_ - 1;
if (mask == 0) {
dx_[idx] = dy_mul_y_reversed_cumsum_[idx] / x_[idx];
if (mid_idx == final_index) {
// record first zero position as -1, i.e., no zero
first_zero_idx_[outer_idx * inner_dim_ + inner_idx] = -1;
}
} else if ((!reverse_ && mid_idx > 0) ||
(reverse_ && mid_idx < mid_dim_ - 1)) { // mask > 0
if ((!reverse_ && zero_mask_[idx - inner_dim_] > 0) ||
(reverse_ && zero_mask_[idx + inner_dim_] > 0)) { // not first zero
dx_[idx] = 0;
should_fill_one = false;
} else {
// idx is the first zero position, it should be recorded
if (exclusive_ && mid_idx == final_index) {
dx_[idx] = 0;
} else if (exclusive_) {
dx_[idx] = y_[idx];
} else {
if (reverse_) {
dx_[idx] = y_[idx + inner_dim_];
} else {
dx_[idx] = y_[idx - inner_dim_];
}
}
first_zero_idx_[outer_idx * inner_dim_ + inner_idx] = mid_idx;
}
} else { // the first zero position is index 0
dx_[idx] = 1;
first_zero_idx_[outer_idx * inner_dim_ + inner_idx] =
reverse_ ? mid_dim_ - 1 : 0;
}
if (exclusive_ && mid_idx == final_index) {
x_filled_one_[idx] = 0;
} else {
x_filled_one_[idx] = should_fill_one ? static_cast<T>(1) : x_[idx];
}
}
private:
const T *x_;
const T *y_;
const T *dy_mul_y_reversed_cumsum_;
const uint8_t *zero_mask_;
size_t mid_dim_;
size_t inner_dim_;
bool exclusive_;
bool reverse_;
T *dx_;
int64_t *first_zero_idx_;
T *x_filled_one_;
};
template <typename T>
struct FillFirstZeroPositionGradFunctor {
HOSTDEVICE FillFirstZeroPositionGradFunctor(const int64_t *first_zero_idx,
const T *grad_value,
size_t mid_dim,
size_t inner_dim,
T *dx)
: first_zero_idx_(first_zero_idx),
grad_value_(grad_value),
mid_dim_(mid_dim),
inner_dim_(inner_dim),
dx_(dx) {}
HOSTDEVICE void operator()(size_t idx) const {
auto outer_idx = idx / inner_dim_;
auto inner_idx = idx % inner_dim_;
auto mid_idx = first_zero_idx_[idx];
if (mid_idx >= 0) {
auto full_idx =
outer_idx * mid_dim_ * inner_dim_ + mid_idx * inner_dim_ + inner_idx;
dx_[full_idx] *= grad_value_[full_idx];
}
}
private:
const int64_t *first_zero_idx_;
const T *grad_value_;
size_t mid_dim_;
size_t inner_dim_;
T *dx_;
};
template <typename T, typename Context>
void ReversedCumsum(const Context &dev_ctx,
const DenseTensor &input,
int dim,
DenseTensor *output) {
DenseTensor flipped_input;
flipped_input.Resize(input.dims());
dev_ctx.template Alloc<T>(&flipped_input);
std::vector<int> axis = {dim};
FlipKernel<T, Context>(dev_ctx, input, axis, &flipped_input);
DenseTensor cumsum_out;
cumsum_out.Resize(input.dims());
dev_ctx.template Alloc<T>(&cumsum_out);
CumsumKernel<T, Context>(
dev_ctx, flipped_input, dim, false, false, false, &cumsum_out);
FlipKernel<T, Context>(dev_ctx, cumsum_out, axis, output);
}
template <typename T, typename Context>
bool CumprodGradCompatible(const Context &dev_ctx,
const DenseTensor &x,
const DenseTensor &out,
const DenseTensor &dout,
int dim,
DenseTensor *dx) {
auto x_dims = x.dims();
int wrap_dim = dim;
if (wrap_dim < 0) {
wrap_dim += x_dims.size();
}
bool is_trivial = (x.numel() <= 1) || (x_dims[wrap_dim] == 1);
if (is_trivial) {
dev_ctx.template Alloc<T>(dx);
Copy(dev_ctx, dout, dev_ctx.GetPlace(), false, dx);
return true;
}
DenseTensor x_conj_tensor;
DenseTensor out_conj_tensor;
if (IsComplexType(x.dtype())) {
x_conj_tensor.Resize(x.dims());
out_conj_tensor.Resize(out.dims());
dev_ctx.template Alloc<T>(&x_conj_tensor);
dev_ctx.template Alloc<T>(&out_conj_tensor);
ConjKernel<T, Context>(dev_ctx, x, &x_conj_tensor);
ConjKernel<T, Context>(dev_ctx, out, &out_conj_tensor);
}
const DenseTensor &x_ref = IsComplexType(x.dtype()) ? x_conj_tensor : x;
const DenseTensor &out_ref = IsComplexType(x.dtype()) ? out_conj_tensor : out;
DenseTensor zero_val;
zero_val.Resize({1});
dev_ctx.template Alloc<T>(&zero_val);
FullKernel<T, Context>(dev_ctx, {1}, static_cast<T>(0), x.dtype(), &zero_val);
DenseTensor is_zero_mask;
is_zero_mask.Resize(x.dims());
dev_ctx.template Alloc<bool>(&is_zero_mask);
EqualKernel<T, Context>(dev_ctx, x, zero_val, &is_zero_mask);
DenseTensor any_zero;
any_zero.Resize({1});
dev_ctx.template Alloc<bool>(&any_zero);
AnyKernel<bool, Context>(
dev_ctx, is_zero_mask, std::vector<int64_t>(), false, &any_zero);
bool has_zero = false;
#ifdef PADDLE_WITH_CUDA
DenseTensor any_zero_cpu;
Copy(dev_ctx, any_zero, CPUPlace(), true, &any_zero_cpu);
has_zero = *any_zero_cpu.data<bool>();
#else
has_zero = *any_zero.data<bool>();
#endif
if (has_zero) {
return false; // fallback
}
dev_ctx.template Alloc<T>(dx);
DenseTensor w;
w.Resize(out_ref.dims());
dev_ctx.template Alloc<T>(&w);
MultiplyKernel<T, Context>(dev_ctx, out_ref, dout, &w);
DenseTensor w_flipped, w_cum, rc_w;
w_flipped.Resize(w.dims());
w_cum.Resize(w.dims());
rc_w.Resize(w.dims());
dev_ctx.template Alloc<T>(&w_flipped);
dev_ctx.template Alloc<T>(&w_cum);
dev_ctx.template Alloc<T>(&rc_w);
std::vector<int> axis = {dim};
FlipKernel<T, Context>(dev_ctx, w, axis, &w_flipped);
CumsumKernel<T, Context>(
dev_ctx, w_flipped, dim, false, false, false, &w_cum);
FlipKernel<T, Context>(dev_ctx, w_cum, axis, &rc_w);
DivideKernel<T, Context>(dev_ctx, rc_w, x_ref, dx);
return true;
}
template <typename T, typename Context>
void CumprodGradKernel(const Context &dev_ctx,
const DenseTensor &x,
const DenseTensor &out,
const DenseTensor &dout,
int dim,
bool exclusive,
bool reverse,
DenseTensor *dx) {
const auto *y = &out;
const auto *dy = &dout;
if (dx && dx->numel() == 0) {
dev_ctx.template Alloc<T>(dx);
return;
}
#ifdef PADDLE_WITH_CUDA
if (FLAGS_use_accuracy_compatible_kernel && !exclusive && !reverse) {
if (CumprodGradCompatible<T, Context>(dev_ctx, x, out, dout, dim, dx)) {
return;
}
}
#endif
size_t outer_dim, mid_dim, inner_dim;
GetCumprodDimInfo(x.dims(), dim, &outer_dim, &mid_dim, &inner_dim);
if (x.dims().size() == 0) {
Copy<Context>(dev_ctx, dout, dev_ctx.GetPlace(), false, dx);
return;
}
if (outer_dim == 0 || mid_dim == 0 || inner_dim == 0) return;
size_t numel = outer_dim * mid_dim * inner_dim;
const auto *x_data = x.data<T>();
const auto *y_data = y->data<T>();
const auto *dy_data = dy->data<T>();
auto place = dev_ctx.GetPlace();
auto *dx_data = dev_ctx.template Alloc<T>(dx);
// deal with complex
const T *x_data_deal;
const T *y_data_deal;
Allocator::AllocationPtr x_conj;
Allocator::AllocationPtr y_conj;
if (IsComplexType(x.dtype())) {
x_conj = const_cast<Allocator &>(dev_ctx.GetAllocator())
.Allocate(numel * sizeof(T));
auto *x_data_conj = reinterpret_cast<T *>(x_conj->ptr());
y_conj = const_cast<Allocator &>(dev_ctx.GetAllocator())
.Allocate(numel * sizeof(T));
auto *y_data_conj = reinterpret_cast<T *>(y_conj->ptr());
funcs::ForRange<Context> for_range_x(dev_ctx, numel);
funcs::ConjFunctor<T> functor_x(x_data, numel, x_data_conj);
for_range_x(functor_x);
funcs::ForRange<Context> for_range_y(dev_ctx, numel);
funcs::ConjFunctor<T> functor_y(y_data, numel, y_data_conj);
for_range_y(functor_y);
x_data_deal = x_data_conj;
y_data_deal = y_data_conj;
} else {
x_data_deal = x_data;
y_data_deal = y_data;
}
// Step 1: find cummax-ed zero mask of x
#ifdef PADDLE_WITH_CUDA
const auto &exec_policy = thrust::cuda::par.on(dev_ctx.stream());
#else
const auto &exec_policy = thrust::hip::par.on(dev_ctx.stream());
#endif
auto zero_mask_without_cummax =
const_cast<Allocator &>(dev_ctx.GetAllocator())
.Allocate(numel * sizeof(uint8_t));
auto *zero_mask_without_cummax_data =
reinterpret_cast<uint8_t *>(zero_mask_without_cummax->ptr());
thrust::transform(exec_policy,
thrust::device_pointer_cast(x_data_deal),
thrust::device_pointer_cast(x_data_deal) + numel,
thrust::device_pointer_cast(zero_mask_without_cummax_data),
funcs::IsZeroFunctor<T>());
auto zero_mask = const_cast<Allocator &>(dev_ctx.GetAllocator())
.Allocate(numel * sizeof(uint8_t));
auto *zero_mask_data = reinterpret_cast<uint8_t *>(zero_mask->ptr());
funcs::InclusiveScan<uint8_t, cub::Max>(zero_mask_without_cummax_data,
zero_mask_data,
outer_dim,
mid_dim,
inner_dim,
static_cast<uint8_t>(0),
cub::Max(),
/*reverse=*/reverse,
dev_ctx); // 计算结果是0的元素mask
zero_mask_without_cummax = nullptr;
// Step 2: calculate reversed cumsum(dy * y)
auto dy_mul_y = const_cast<Allocator &>(dev_ctx.GetAllocator())
.Allocate(numel * sizeof(T));
auto *dy_mul_y_data = reinterpret_cast<T *>(dy_mul_y->ptr());
thrust::transform(exec_policy,
thrust::device_pointer_cast(dy_data),
thrust::device_pointer_cast(dy_data) + numel,
thrust::device_pointer_cast(y_data_deal),
thrust::device_pointer_cast(dy_mul_y_data),
funcs::MultiplyFunctor<T>());
auto dy_mul_y_reversed_cumsum =
const_cast<Allocator &>(dev_ctx.GetAllocator())
.Allocate(numel * sizeof(T));
auto *dy_mul_y_reversed_cumsum_data =
reinterpret_cast<T *>(dy_mul_y_reversed_cumsum->ptr());
if (exclusive) {
funcs::ExclusiveScan<T, cub::Sum>(dy_mul_y_data,
dy_mul_y_reversed_cumsum_data,
outer_dim,
mid_dim,
inner_dim,
static_cast<T>(0.0f),
cub::Sum(),
/*reverse=*/!reverse,
dev_ctx);
} else {
funcs::InclusiveScan<T, cub::Sum>(dy_mul_y_data,
dy_mul_y_reversed_cumsum_data,
outer_dim,
mid_dim,
inner_dim,
static_cast<T>(0.0f),
cub::Sum(),
/*reverse=*/!reverse,
dev_ctx);
}
// Step 3: calculate the gradient value except the first zero position.
// The gradient value of the first zero position is filled with out[idx-1],
// while the gradient value of the other positions are calculated out
// completely. This functor also:
// (1) find the first zero index, i.e., first_zero_idx_data.
// (2) fill x_filled_one, which satisfies
// x_filled_one[i] = x[i], i > pos
// x_filled_one[i] = 1, i <= pos
auto first_zero_idx = const_cast<Allocator &>(dev_ctx.GetAllocator())
.Allocate(numel * sizeof(int64_t));
auto *first_zero_idx_data =
reinterpret_cast<int64_t *>(first_zero_idx->ptr());
auto *x_filled_one_data = dy_mul_y_data; // reuse former allocated memory
funcs::ForRange<Context> for_range(dev_ctx, numel);
CumprodGradFunctorExceptFirstZero<T> functor_except_first_zero(
x_data_deal,
y_data_deal,
dy_mul_y_reversed_cumsum_data,
zero_mask_data,
mid_dim,
inner_dim,
exclusive,
reverse,
dx_data,
first_zero_idx_data,
x_filled_one_data);
for_range(functor_except_first_zero); // set the element after the first 0 to
// 1 [1,2,3,0,4,5] -> [1,1,1,1,4,5]
// Step 4: calculate cumprod of x_filled_one
auto *x_filled_one_cumprod_data =
dy_mul_y_reversed_cumsum_data; // reuse former allocated memory
funcs::InclusiveScan<T, funcs::MultiplyFunctor<T>>(
x_filled_one_data,
x_filled_one_cumprod_data,
outer_dim,
mid_dim,
inner_dim,
static_cast<T>(1.0f),
funcs::MultiplyFunctor<T>(),
/*reverse=*/reverse,
dev_ctx); // 累乘 [1,1,1,1,4,5] -> [1,1,1,1,4,20]
// }
// Step 5: calculate reversed cumsum(dy * x_filled_one_cumprod)
auto *dy_mul_x_filled_one_cumprod =
dy_mul_y_data; // reuse former allocated memory
thrust::transform(exec_policy,
thrust::device_pointer_cast(dy_data),
thrust::device_pointer_cast(dy_data) + numel,
thrust::device_pointer_cast(x_filled_one_cumprod_data),
thrust::device_pointer_cast(dy_mul_x_filled_one_cumprod),
funcs::MultiplyFunctor<T>());
auto *dy_mul_x_filled_one_cumprod_reversed_cumsum =
dy_mul_y_reversed_cumsum_data; // reuse former allocated memory
funcs::InclusiveScan<T, cub::Sum>(
dy_mul_x_filled_one_cumprod,
dy_mul_x_filled_one_cumprod_reversed_cumsum,
outer_dim,
mid_dim,
inner_dim,
static_cast<T>(0.0f),
cub::Sum(),
/*reverse=*/!reverse,
dev_ctx); // 反向累加 [1,1,1,1,4,20] -> [28,27,26,25,24,20]
// Step 6: fill zero pos gradient value
funcs::ForRange<Context> for_range_fill_zero_pos_grad(dev_ctx,
outer_dim * inner_dim);
FillFirstZeroPositionGradFunctor<T> fill_first_zero_pos_grad_functor(
first_zero_idx_data,
dy_mul_x_filled_one_cumprod_reversed_cumsum,
mid_dim,
inner_dim,
dx_data);
for_range_fill_zero_pos_grad(
fill_first_zero_pos_grad_functor); // use new grad as the grad of first 0
}
} // namespace phi
PD_REGISTER_KERNEL(cumprod_grad,
GPU,
ALL_LAYOUT,
phi::CumprodGradKernel,
float,
double,
int,
int64_t,
phi::float16,
phi::bfloat16,
phi::complex64,
phi::complex128) {}