Files
paddlepaddle--paddle/paddle/phi/kernels/impl/slogdeterminant_grad_kernel_impl.h
T
2026-07-13 12:40:42 +08:00

292 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.
#pragma once
#include "glog/logging.h"
#include "paddle/phi/core/tensor_utils.h"
#include "paddle/phi/core/utils/data_type.h"
#include "paddle/phi/kernels/complex_kernel.h"
#include "paddle/phi/kernels/elementwise_add_kernel.h"
#include "paddle/phi/kernels/elementwise_multiply_kernel.h"
#include "paddle/phi/kernels/full_kernel.h"
#include "paddle/phi/kernels/funcs/complex_functors.h"
#include "paddle/phi/kernels/funcs/math_function.h"
#include "paddle/phi/kernels/funcs/matrix_inverse.h"
#include "paddle/phi/kernels/funcs/unsqueeze.h"
#include "paddle/phi/kernels/impl/determinant_grad_kernel_impl.h"
#include "paddle/phi/kernels/impl/isfinite_kernel_impl.h"
#include "paddle/phi/kernels/slogdeterminant_grad_kernel.h"
#include "paddle/phi/kernels/transpose_kernel.h"
namespace phi {
template <typename T, typename Context>
void SlogDeterminantGradKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& out,
const DenseTensor& out_grad,
DenseTensor* x_grad) {
if (x_grad && x_grad->numel() == 0) {
dev_ctx.template Alloc<T>(x_grad);
return;
}
PADDLE_ENFORCE_EQ(
out_grad.dims()[0],
2,
errors::InvalidArgument("The grad tensor of SlogDet should contain two"
" grad: sign and absslogdet, but here %ld.",
out_grad.dims()[0]));
if (x.dims().size() > 2) {
PADDLE_ENFORCE_EQ(
out_grad.dims().size() + 1,
x.dims().size(),
errors::InvalidArgument(
"The grad tensor of slogdet dims size should 1 less than"
" input tensor's, but here differ %d",
x.dims().size() - out_grad.dims().size()));
}
// Check Whether the matrix is invertible
// (matrix A not invertible) == (absslogdet(A)=0)
auto slogdet_vec = out.Split(1, 0);
auto absslogdet_val = slogdet_vec[0];
if (!detail::CheckMatrixInvertible<T, Context>(dev_ctx, &absslogdet_val)) {
// The matrix is not invertible
VLOG(3) << "The input matrix not invertible!";
x_grad->Resize(x.dims());
Full<T>(dev_ctx,
vectorize(x.dims()),
std::numeric_limits<T>::quiet_NaN(),
x_grad);
return;
}
// The matrix is invertible
// let sl|A| = SlogDeterminant(A)
// Ref to https://people.maths.ox.ac.uk/gilesm/files/NA-08-01.pdf
// we set dsl|A| = unsqueeze(dslA, [-1, -2]) *
// inverse(A).conj().transpose(-2, -1)
// First: inverse(A)
DenseTensor inverse_A;
// A must be square matrices!
inverse_A.Resize(x.dims());
dev_ctx.template Alloc<T>(&inverse_A);
const auto& mat_dims = x.dims();
const int rank = mat_dims.size();
int n = mat_dims[rank - 1];
int64_t total_batch_size = rank > 2 ? x.numel() / (n * n) : 1;
// Divide the batch into chunks because of cublasMatInv limitation
if (total_batch_size <= 65536) {
funcs::MatrixInverseFunctor<Context, T> mat_inv;
mat_inv(dev_ctx, x, &inverse_A);
} else {
constexpr int64_t max_batch_size = 65536;
int64_t processed = 0;
VLOG(3) << "Large batch size detected (" << total_batch_size
<< "), processing in chunks of " << max_batch_size;
while (processed < total_batch_size) {
int64_t current_batch =
std::min(max_batch_size, total_batch_size - processed);
// Extract current batch data
DenseTensor x_batch;
x_batch.ShareDataWith(x);
x_batch.Resize({total_batch_size, n, n});
x_batch = x_batch.Slice(processed, processed + current_batch);
x_batch.Resize({current_batch, n, n});
DenseTensor inverse_batch;
inverse_batch.Resize({current_batch, n, n});
dev_ctx.template Alloc<T>(&inverse_batch);
// Compute the inverse matrix for the current batch
funcs::MatrixInverseFunctor<Context, T> mat_inv;
mat_inv(dev_ctx, x_batch, &inverse_batch);
// Copy the result to the output tensor
DenseTensor output_slice;
output_slice.ShareDataWith(inverse_A);
output_slice.Resize({total_batch_size, n, n});
output_slice = output_slice.Slice(processed, processed + current_batch);
output_slice.Resize({current_batch, n, n});
Copy(dev_ctx, inverse_batch, dev_ctx.GetPlace(), false, &output_slice);
processed += current_batch;
}
}
VLOG(3) << "inverse(A) dims: " << inverse_A.dims();
// Second: inverse(A).conj()
auto conj_inverse_A = Conj<T>(dev_ctx, inverse_A);
VLOG(3) << "inverse(A).conj() dims: " << conj_inverse_A.dims();
// Third: inverse(A).conj().transpose(-2, -1)
DenseTensor transpose_inverse_A =
TransposeLast2Dim<T>(dev_ctx, conj_inverse_A);
VLOG(3) << "inverse(A).conj().transpose(-2, -1) dims: "
<< transpose_inverse_A.dims();
// Fourth: split grad value to [sign_grad, absslogdet_grad]
auto grad_vec = out_grad.Split(1, 0);
auto det_grad = grad_vec[1];
// remove useless first dimension
int det_grad_size = det_grad.dims().size();
std::vector<int64_t> det_grad_vec;
for (int64_t i = 1; i < det_grad_size; ++i) {
det_grad_vec.emplace_back(det_grad.dims()[i]);
}
det_grad.Resize(det_grad.dims().reshape(det_grad_vec));
// Fifth: unsqueeze(dslA, [-1, -2])
auto unsqueeze1 = funcs::Unsqueeze(det_grad, -1);
auto unsqueeze2 = funcs::Unsqueeze(unsqueeze1, -2);
VLOG(3) << "unsqueezed(dslA, [-1, -2]) dims: " << unsqueeze2.dims();
// Finally: unsqueeze(dslA) * inverse(A)
auto res = Multiply<T>(dev_ctx, unsqueeze2, transpose_inverse_A);
VLOG(3) << "unsqueeze(dslA) * inverse(A) dims: " << res.dims();
Copy(dev_ctx, res, dev_ctx.GetPlace(), false, x_grad);
x_grad->Resize(x.dims());
VLOG(3) << "dsl|A| dims: " << x_grad->dims();
}
template <typename T, typename Context>
void SlogDeterminantV2GradKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& sign,
const DenseTensor& logdet,
const DenseTensor& sign_grad UNUSED,
const DenseTensor& logdet_grad,
DenseTensor* x_grad) {
using RealT = typename dtype::Real<T>;
const auto& x_dims = x.dims();
const auto& grad_dims = logdet_grad.dims();
int x_rank = x_dims.size();
int grad_rank = grad_dims.size();
PADDLE_ENFORCE_GE(
x_rank,
2,
common::errors::InvalidArgument(
"Input tensor X's rank must be at least 2, but received %d.",
x_rank));
if (x_rank == 2)
PADDLE_ENFORCE_EQ(
grad_rank,
0,
common::errors::InvalidArgument(
"For a 2D input tensor X, the gradient tensor (logdet_grad) "
"should be a 0D tensor (scalar), but received rank %d.",
grad_rank));
else if (x_rank > 2)
PADDLE_ENFORCE_EQ(
grad_rank + 2,
x_rank,
common::errors::InvalidArgument(
"The rank of gradient tensor (logdet_grad) should be 2 less than "
"the input tensor X's rank, but received grad rank %d and X rank "
"%d.",
grad_rank,
x_rank));
dev_ctx.template Alloc<T>(x_grad);
if (x_grad->numel() == 0) {
return;
}
// Check Whether the matrix is invertible
// (matrix A not invertible) == (absslogdet(A)=0)
if (!detail::CheckMatrixInvertible<RealT, Context>(dev_ctx, &logdet)) {
// The matrix is not invertible
VLOG(3) << "The input matrix not invertible!";
Full<T>(dev_ctx,
vectorize(x.dims()),
std::numeric_limits<T>::quiet_NaN(),
x_grad);
return;
}
// The matrix is invertible
// let sl|A| = SlogDeterminant(A)
// Ref to https://people.maths.ox.ac.uk/gilesm/files/NA-08-01.pdf
// we set dsl|A| = unsqueeze(dslA, [-1, -2]) *
// inverse(A).conj().transpose(-2, -1)
// First: inverse(A)
DenseTensor inverse_A;
// A must be square matrices!
inverse_A.Resize(x_dims);
dev_ctx.template Alloc<T>(&inverse_A);
funcs::MatrixInverseFunctor<Context, T> mat_inv;
mat_inv(dev_ctx, x, &inverse_A);
VLOG(3) << "inverse(A) dims: " << inverse_A.dims();
// Second: inverse(A).conj() for complex
DenseTensor conj_inverse_A;
if constexpr (is_complex64_or_complex128<T>::value) {
conj_inverse_A = Conj<T>(dev_ctx, inverse_A);
VLOG(3) << "Performed complex conjugate.";
} else {
conj_inverse_A.ShareDataWith(inverse_A);
VLOG(3) << "Skipped complex conjugate for real type.";
}
VLOG(3) << "inverse(A).conj() dims: " << conj_inverse_A.dims();
// Third: inverse(A).conj().transpose(-2, -1)
DenseTensor transpose_inverse_A =
TransposeLast2Dim<T>(dev_ctx, conj_inverse_A);
VLOG(3) << "inverse(A).conj().transpose(-2, -1) dims: "
<< transpose_inverse_A.dims();
DenseTensor logdet_grad_term = logdet_grad;
if constexpr (is_complex64_or_complex128<T>::value) {
// change logdet_grad datatype from <RealT> to <ComplexT>
DenseTensor logdet_grad_complex = Empty<T>(dev_ctx, vectorize(grad_dims));
int64_t logdet_numel = logdet_grad.numel();
funcs::ForRange<Context> for_range(dev_ctx, logdet_numel);
funcs::RealToComplexFunctor<T> functor(
logdet_grad.data<RealT>(), logdet_grad_complex.data<T>(), logdet_numel);
for_range(functor);
logdet_grad_term = logdet_grad_complex;
}
DenseTensor unsqueezed_combined_grad = funcs::Unsqueeze(logdet_grad_term, -1);
unsqueezed_combined_grad = funcs::Unsqueeze(unsqueezed_combined_grad, -2);
VLOG(3) << "unsqueezed_combined_grad dims: "
<< unsqueezed_combined_grad.dims();
Multiply<T, Context>(
dev_ctx, unsqueezed_combined_grad, transpose_inverse_A, x_grad);
VLOG(3) << x_grad->dims();
}
} // namespace phi