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

317 lines
11 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 <algorithm>
#include <cmath>
#include <complex>
#include "paddle/phi/backends/cpu/cpu_context.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/full_kernel.h"
#include "paddle/phi/kernels/funcs/complex_functors.h"
#include "paddle/phi/kernels/funcs/lapack/lapack_function.h"
#include "paddle/phi/kernels/funcs/math_function.h"
#include "paddle/phi/kernels/impl/lstsq_kernel_impl.h"
#include "paddle/phi/kernels/lstsq_kernel.h"
#include "paddle/phi/kernels/transpose_kernel.h"
namespace phi {
enum class LapackDriverType : int { Gels, Gelsd, Gelsy, Gelss };
template <typename T, typename Context>
void LstsqKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& y,
const Scalar& rcond_scaler,
const std::string& driver_string,
DenseTensor* solution,
DenseTensor* residuals,
DenseTensor* rank,
DenseTensor* singular_values) {
using ValueType = dtype::Real<T>;
if (x.numel() == 0 || y.numel() == 0) {
if (solution) Full<T, Context>(dev_ctx, solution->dims(), 0, solution);
if (rank) Full<int64_t, Context>(dev_ctx, rank->dims(), 0, rank);
if (residuals)
GetResidualsTensor<Context, T>(
dev_ctx, x, y, driver_string, solution, residuals, rank);
if (singular_values)
Full<T, Context>(dev_ctx, singular_values->dims(), 0, singular_values);
return;
}
static auto driver_type = std::unordered_map<std::string, LapackDriverType>(
{{"gels", LapackDriverType::Gels},
{"gelsy", LapackDriverType::Gelsy},
{"gelsd", LapackDriverType::Gelsd},
{"gelss", LapackDriverType::Gelss}});
auto driver = driver_type[driver_string];
T rcond = rcond_scaler.to<T>();
auto x_dims = x.dims();
auto y_dims = y.dims();
int dim_size = x_dims.size();
int x_stride = GetMatrixStride(x_dims);
int y_stride = GetMatrixStride(y_dims);
int batch_count = GetBatchCount(x_dims);
auto solution_dim = solution->dims();
int ori_solu_stride = GetMatrixStride(solution_dim);
int max_solu_stride = std::max(y_stride, ori_solu_stride);
int min_solu_stride = std::min(y_stride, ori_solu_stride);
// lapack is a column-major storage, transpose make the input to
// have a continuous memory layout
int info = 0;
int m = static_cast<int>(x_dims[dim_size - 2]);
int n = static_cast<int>(x_dims[dim_size - 1]);
int nrhs = static_cast<int>(y_dims[dim_size - 1]);
int lda = std::max<int>(m, 1);
int ldb = std::max<int>(1, std::max(m, n));
DenseTensor new_x;
new_x.Resize({batch_count, m, n});
dev_ctx.template Alloc<T>(&new_x);
Copy<Context>(dev_ctx, x, dev_ctx.GetPlace(), true, &new_x);
solution->Resize({batch_count, std::max(m, n), nrhs});
dev_ctx.template Alloc<T>(solution);
if (m >= n) {
Copy<Context>(dev_ctx, y, dev_ctx.GetPlace(), true, solution);
} else {
auto* solu_data = solution->data<T>();
auto* y_data = y.data<T>();
for (auto i = 0; i < batch_count; i++) {
for (auto j = 0; j < min_solu_stride; j++) {
solu_data[i * max_solu_stride + j] = y_data[i * y_stride + j];
}
}
}
DenseTensor input_x_trans = TransposeLast2Dim<T>(dev_ctx, new_x);
DenseTensor input_y_trans = TransposeLast2Dim<T>(dev_ctx, *solution);
Copy<Context>(dev_ctx, input_x_trans, dev_ctx.GetPlace(), true, &new_x);
Copy<Context>(dev_ctx, input_y_trans, dev_ctx.GetPlace(), true, solution);
auto* x_vector = new_x.data<T>();
auto* y_vector = solution->data<T>();
// "gels" divers does not need to compute rank
int rank_32 = 0;
int* rank_data = nullptr;
int* rank_working_ptr = nullptr;
if (driver != LapackDriverType::Gels) {
rank_data = dev_ctx.template Alloc<int>(rank);
rank_working_ptr = rank_data;
}
// "gelsd" and "gelss" divers need to compute singular values
ValueType* s_data = nullptr;
ValueType* s_working_ptr = nullptr;
int s_stride = 0;
if (driver == LapackDriverType::Gelsd || driver == LapackDriverType::Gelss) {
s_data = dev_ctx.template Alloc<T>(singular_values);
s_working_ptr = s_data;
auto s_dims = singular_values->dims();
s_stride = static_cast<int>(s_dims[s_dims.size() - 1]);
}
// "jpvt" is only used for "gelsy" driver
DenseTensor jpvt;
int* jpvt_data = nullptr;
if (driver == LapackDriverType::Gelsy) {
jpvt.Resize({std::max<int>(1, n)});
jpvt_data = dev_ctx.template Alloc<int>(&jpvt);
}
// run once the driver, first to get the optimal workspace size
int lwork = -1;
T wkopt = 0.0;
ValueType rwkopt;
int iwkopt = 0;
if (driver == LapackDriverType::Gels) {
funcs::lapackGels(
'N', m, n, nrhs, x_vector, lda, y_vector, ldb, &wkopt, lwork, &info);
} else if (driver == LapackDriverType::Gelsd) {
funcs::lapackGelsd(m,
n,
nrhs,
x_vector,
lda,
y_vector,
ldb,
s_working_ptr,
static_cast<ValueType>(rcond),
&rank_32,
&wkopt,
lwork,
&rwkopt,
&iwkopt,
&info);
} else if (driver == LapackDriverType::Gelsy) {
funcs::lapackGelsy(m,
n,
nrhs,
x_vector,
lda,
y_vector,
ldb,
jpvt_data,
static_cast<ValueType>(rcond),
&rank_32,
&wkopt,
lwork,
&rwkopt,
&info);
} else if (driver == LapackDriverType::Gelss) {
funcs::lapackGelss(m,
n,
nrhs,
x_vector,
lda,
y_vector,
ldb,
s_working_ptr,
static_cast<ValueType>(rcond),
&rank_32,
&wkopt,
lwork,
&rwkopt,
&info);
}
lwork = std::max<int>(1, static_cast<int>(dtype::Real<T>(wkopt)));
DenseTensor work;
work.Resize({lwork});
T* work_data = dev_ctx.template Alloc<T>(&work);
// "rwork" only used for complex inputs and "gelsy/gelsd/gelss" drivers
DenseTensor rwork;
ValueType* rwork_data = nullptr;
if (IsComplexDtype(x.dtype()) && driver != LapackDriverType::Gels) {
int rwork_len = 0;
if (driver == LapackDriverType::Gelsy) {
rwork_len = std::max<int>(1, 2 * n);
} else if (driver == LapackDriverType::Gelss) {
rwork_len = std::max<int>(1, 5 * std::min(m, n));
} else if (driver == LapackDriverType::Gelsd) {
rwork_len = std::max<int>(1, rwkopt);
}
rwork.Resize({rwork_len});
rwork_data = dev_ctx.template Alloc<ValueType>(&rwork);
}
// "iwork" workspace array is relevant only for "gelsd" driver
DenseTensor iwork;
int* iwork_data = nullptr;
if (driver == LapackDriverType::Gelsd) {
iwork.Resize({std::max<int>(1, iwkopt)});
iwork_data = dev_ctx.template Alloc<int>(&iwork);
}
for (auto i = 0; i < batch_count; ++i) {
auto* x_input = &x_vector[i * x_stride];
auto* y_input = &y_vector[i * max_solu_stride];
rank_working_ptr = rank_working_ptr ? &rank_data[i] : nullptr;
s_working_ptr = s_working_ptr ? &s_data[i * s_stride] : nullptr;
if (driver == LapackDriverType::Gels) {
funcs::lapackGels(
'N', m, n, nrhs, x_input, lda, y_input, ldb, work_data, lwork, &info);
} else if (driver == LapackDriverType::Gelsd) {
funcs::lapackGelsd(m,
n,
nrhs,
x_input,
lda,
y_input,
ldb,
s_working_ptr,
static_cast<ValueType>(rcond),
&rank_32,
work_data,
lwork,
rwork_data,
iwork_data,
&info);
} else if (driver == LapackDriverType::Gelsy) {
funcs::lapackGelsy(m,
n,
nrhs,
x_input,
lda,
y_input,
ldb,
jpvt_data,
static_cast<ValueType>(rcond),
&rank_32,
work_data,
lwork,
rwork_data,
&info);
} else if (driver == LapackDriverType::Gelss) {
funcs::lapackGelss(m,
n,
nrhs,
x_input,
lda,
y_input,
ldb,
s_working_ptr,
static_cast<ValueType>(rcond),
&rank_32,
work_data,
lwork,
rwork_data,
&info);
}
PADDLE_ENFORCE_EQ(
info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: Lapack info is not zero but [%d]", i, info));
if (rank_working_ptr) *rank_working_ptr = static_cast<int>(rank_32);
}
DenseTensor tmp_s = TransposeLast2Dim<T>(dev_ctx, *solution);
Copy<Context>(dev_ctx, tmp_s, dev_ctx.GetPlace(), true, solution);
if (m > n) {
auto* solu_data = solution->data<T>();
for (auto i = 1; i < batch_count; i++) {
for (auto j = 0; j < min_solu_stride; j++) {
solu_data[i * min_solu_stride + j] = solu_data[i * max_solu_stride + j];
}
}
}
if (batch_count > 1) {
solution->Resize(solution_dim);
} else {
solution->Resize({n, nrhs});
}
GetResidualsTensor<Context, T>(
dev_ctx, x, y, driver_string, solution, residuals, rank);
}
} // namespace phi
PD_REGISTER_KERNEL(lstsq, CPU, ALL_LAYOUT, phi::LstsqKernel, float, double) {
kernel->OutputAt(2).SetDataType(phi::DataType::INT32);
}