mirror of
https://github.com/ceres-solver/ceres-solver.git
synced 2026-08-29 08:34:37 +08:00
0ca2db57c7
Change-Id: I0038f9c91dc70c422c01305dc10fca5a22a2f0d0
488 lines
18 KiB
C++
488 lines
18 KiB
C++
// Ceres Solver - A fast non-linear least squares minimizer
|
|
// Copyright 2024 Google Inc. All rights reserved.
|
|
// http://ceres-solver.org/
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// * Neither the name of Google Inc. nor the names of its contributors may be
|
|
// used to endorse or promote products derived from this software without
|
|
// specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Author: markshachkov@gmail.com (Mark Shachkov)
|
|
|
|
#include "ceres/cuda_sparse_cholesky.h"
|
|
|
|
#ifndef CERES_NO_CUDSS
|
|
|
|
#include <cstddef>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
#include "Eigen/Core"
|
|
#include "absl/log/check.h"
|
|
#include "absl/log/log.h"
|
|
#include "ceres/compressed_row_sparse_matrix.h"
|
|
#include "ceres/cuda_buffer.h"
|
|
#include "ceres/linear_solver.h"
|
|
#include "cudss.h"
|
|
|
|
namespace ceres::internal {
|
|
|
|
inline std::string cuDSSStatusToString(cudssStatus_t status) {
|
|
switch (status) {
|
|
case CUDSS_STATUS_SUCCESS:
|
|
return "CUDSS_STATUS_SUCCESS";
|
|
case CUDSS_STATUS_NOT_INITIALIZED:
|
|
return "CUDSS_STATUS_NOT_INITIALIZED";
|
|
case CUDSS_STATUS_ALLOC_FAILED:
|
|
return "CUDSS_STATUS_ALLOC_FAILED";
|
|
case CUDSS_STATUS_INVALID_VALUE:
|
|
return "CUDSS_STATUS_INVALID_VALUE";
|
|
case CUDSS_STATUS_NOT_SUPPORTED:
|
|
return "CUDSS_STATUS_NOT_SUPPORTED";
|
|
case CUDSS_STATUS_EXECUTION_FAILED:
|
|
return "CUDSS_STATUS_EXECUTION_FAILED";
|
|
case CUDSS_STATUS_INTERNAL_ERROR:
|
|
return "CUDSS_STATUS_INTERNAL_ERROR";
|
|
default:
|
|
return "unknown cuDSS status: " + std::to_string(status);
|
|
}
|
|
}
|
|
|
|
#define CUDSS_STATUS_CHECK(IN) \
|
|
if (cudssStatus_t status = IN; status != CUDSS_STATUS_SUCCESS) { \
|
|
CHECK(false) << "Got error: " << cuDSSStatusToString(status); \
|
|
}
|
|
|
|
#define CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(IN, additional_message) \
|
|
if (cudssStatus_t status = IN; status != CUDSS_STATUS_SUCCESS) { \
|
|
*message = std::string(additional_message) + \
|
|
" Got error: " + cuDSSStatusToString(status); \
|
|
return factorize_result_ = LinearSolverTerminationType::FATAL_ERROR; \
|
|
}
|
|
|
|
#define CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS(IN) \
|
|
if (cudssStatus_t status = IN; status != CUDSS_STATUS_SUCCESS) { \
|
|
return status; \
|
|
}
|
|
|
|
class CERES_NO_EXPORT CuDSSMatrixBase {
|
|
public:
|
|
CuDSSMatrixBase() = default;
|
|
CuDSSMatrixBase(const CuDSSMatrixBase&) = delete;
|
|
CuDSSMatrixBase(CuDSSMatrixBase&&) = delete;
|
|
CuDSSMatrixBase& operator=(const CuDSSMatrixBase&) = delete;
|
|
CuDSSMatrixBase& operator=(CuDSSMatrixBase&&) = delete;
|
|
~CuDSSMatrixBase() { CUDSS_STATUS_CHECK(Free()); }
|
|
|
|
cudssStatus_t Free() noexcept {
|
|
if (matrix_) {
|
|
const auto status = cudssMatrixDestroy(matrix_);
|
|
matrix_ = nullptr;
|
|
return status;
|
|
}
|
|
|
|
return CUDSS_STATUS_SUCCESS;
|
|
}
|
|
|
|
cudssMatrix_t Get() const noexcept { return matrix_; }
|
|
|
|
protected:
|
|
cudssMatrix_t matrix_{nullptr};
|
|
};
|
|
|
|
class CERES_NO_EXPORT CuDSSMatrixCSR : public CuDSSMatrixBase {
|
|
public:
|
|
cudssStatus_t Reset(int64_t num_rows,
|
|
int64_t num_cols,
|
|
int64_t num_nonzeros,
|
|
void* rows_start,
|
|
void* rows_end,
|
|
void* cols,
|
|
void* values,
|
|
cudaDataType_t index_type,
|
|
cudaDataType_t value_type,
|
|
cudssMatrixType_t matrix_type,
|
|
cudssMatrixViewType_t matrix_storage_type,
|
|
cudssIndexBase_t index_base) {
|
|
CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS(Free());
|
|
|
|
return cudssMatrixCreateCsr(&matrix_,
|
|
num_rows,
|
|
num_cols,
|
|
num_nonzeros,
|
|
rows_start,
|
|
rows_end,
|
|
cols,
|
|
values,
|
|
index_type,
|
|
value_type,
|
|
matrix_type,
|
|
matrix_storage_type,
|
|
index_base);
|
|
}
|
|
};
|
|
|
|
class CERES_NO_EXPORT CuDSSMatrixDense : public CuDSSMatrixBase {
|
|
public:
|
|
cudssStatus_t Reset(int64_t num_rows,
|
|
int64_t num_cols,
|
|
int64_t leading_dimension_size,
|
|
void* values,
|
|
cudaDataType_t value_type,
|
|
cudssLayout_t layout) {
|
|
CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS(Free());
|
|
|
|
return cudssMatrixCreateDn(&matrix_,
|
|
num_rows,
|
|
num_cols,
|
|
leading_dimension_size,
|
|
values,
|
|
value_type,
|
|
layout);
|
|
}
|
|
};
|
|
|
|
struct CudssContext {
|
|
CudssContext(cudssHandle_t cudss_handle) : cudss_handle_(cudss_handle) {
|
|
CUDSS_STATUS_CHECK(cudssConfigCreate(&solver_config_));
|
|
CUDSS_STATUS_CHECK(cudssDataCreate(cudss_handle_, &solver_data_));
|
|
}
|
|
CudssContext(const CudssContext&) = delete;
|
|
CudssContext(CudssContext&&) = delete;
|
|
CudssContext& operator=(const CudssContext&) = delete;
|
|
CudssContext& operator=(CudssContext&&) = delete;
|
|
~CudssContext() {
|
|
CUDSS_STATUS_CHECK(cudssDataDestroy(cudss_handle_, solver_data_));
|
|
CUDSS_STATUS_CHECK(cudssConfigDestroy(solver_config_));
|
|
}
|
|
|
|
cudssHandle_t cudss_handle_{nullptr};
|
|
cudssConfig_t solver_config_{nullptr};
|
|
cudssData_t solver_data_{nullptr};
|
|
};
|
|
|
|
template <typename Scalar>
|
|
class CERES_NO_EXPORT CudaSparseCholeskyImpl final : public SparseCholesky {
|
|
public:
|
|
static_assert(std::is_same_v<Scalar, float> || std::is_same_v<Scalar, double>,
|
|
"Scalar type is unsupported by cuDSS");
|
|
static constexpr cudaDataType_t kCuDSSScalar =
|
|
std::is_same_v<Scalar, float> ? CUDA_R_32F : CUDA_R_64F;
|
|
|
|
CudaSparseCholeskyImpl(ContextImpl* context)
|
|
: context_(context),
|
|
lhs_cols_d_(context_),
|
|
lhs_rows_d_(context_),
|
|
lhs_values_d_(context_),
|
|
rhs_d_(context_),
|
|
x_d_(context_) {}
|
|
CudaSparseCholeskyImpl(const CudaSparseCholeskyImpl&) = delete;
|
|
CudaSparseCholeskyImpl(CudaSparseCholeskyImpl&&) = delete;
|
|
CudaSparseCholeskyImpl& operator=(const CudaSparseCholeskyImpl&) = delete;
|
|
CudaSparseCholeskyImpl& operator=(CudaSparseCholeskyImpl&&) = delete;
|
|
~CudaSparseCholeskyImpl() = default;
|
|
|
|
CompressedRowSparseMatrix::StorageType StorageType() const {
|
|
return CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR;
|
|
}
|
|
|
|
LinearSolverTerminationType Factorize(CompressedRowSparseMatrix* lhs,
|
|
std::string* message) {
|
|
if (lhs->num_rows() != lhs->num_cols()) {
|
|
*message = "lhs matrix must be square";
|
|
return factorize_result_ = LinearSolverTerminationType::FATAL_ERROR;
|
|
}
|
|
if (lhs->storage_type() != StorageType()) {
|
|
*message = "lhs matrix must be lower triangular";
|
|
return factorize_result_ = LinearSolverTerminationType::FATAL_ERROR;
|
|
}
|
|
|
|
// If, after previous attempt to factorize, cudssDataGet(CUDSS_DATA_INFO)
|
|
// returned a numerical error, such error will be preserved by cuDSS 0.3.0
|
|
// and returned by cudssDataGet(CUDSS_DATA_INFO) even after correctly
|
|
// factorizeable matrix is provided. Such behaviour forces us to reset a
|
|
// cudssData_t object (managed by CudssContext) and to loose a result of
|
|
// anylyze stage, thus we have to perform analyze one more time.
|
|
// TODO: do not re-perform analyze in case of failed factorization numerics
|
|
if (analyze_result_ != LinearSolverTerminationType::SUCCESS ||
|
|
factorize_result_ != LinearSolverTerminationType::SUCCESS) {
|
|
analyze_result_ = Analyze(lhs, message);
|
|
if (analyze_result_ != LinearSolverTerminationType::SUCCESS) {
|
|
return analyze_result_;
|
|
}
|
|
}
|
|
CHECK_NE(cudss_context_.get(), nullptr);
|
|
|
|
ConvertAndCopyToDevice(lhs->values(), lhs_values_h_.data(), lhs_values_d_);
|
|
|
|
CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
|
|
cudssExecute(context_->cudss_handle_,
|
|
CUDSS_PHASE_FACTORIZATION,
|
|
cudss_context_->solver_config_,
|
|
cudss_context_->solver_data_,
|
|
cudss_lhs_.Get(),
|
|
cudss_x_.Get(),
|
|
cudss_rhs_.Get()),
|
|
"cudssExecute with CUDSS_PHASE_FACTORIZATION failed");
|
|
|
|
int cudss_data_info;
|
|
CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
|
|
GetCudssDataInfo(cudss_data_info),
|
|
"cudssDataGet with CUDSS_DATA_INFO failed");
|
|
const auto factorization_status =
|
|
static_cast<cudssStatus_t>(cudss_data_info);
|
|
|
|
if (factorization_status == CUDSS_STATUS_SUCCESS) {
|
|
return factorize_result_ = LinearSolverTerminationType::SUCCESS;
|
|
}
|
|
|
|
if (cudss_data_info > 0) {
|
|
return factorize_result_ = LinearSolverTerminationType::FAILURE;
|
|
}
|
|
|
|
return factorize_result_ = LinearSolverTerminationType::FATAL_ERROR;
|
|
}
|
|
|
|
LinearSolverTerminationType Solve(const double* rhs,
|
|
double* solution,
|
|
std::string* message) {
|
|
CHECK_NE(cudss_context_.get(), nullptr);
|
|
|
|
if (factorize_result_ != LinearSolverTerminationType::SUCCESS) {
|
|
*message = "Factorize did not complete successfully previously.";
|
|
return factorize_result_;
|
|
}
|
|
|
|
ConvertAndCopyToDevice(rhs, rhs_h_.data(), rhs_d_);
|
|
|
|
CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
|
|
cudssExecute(context_->cudss_handle_,
|
|
CUDSS_PHASE_SOLVE,
|
|
cudss_context_->solver_config_,
|
|
cudss_context_->solver_data_,
|
|
cudss_lhs_.Get(),
|
|
cudss_x_.Get(),
|
|
cudss_rhs_.Get()),
|
|
"cudssExecute with CUDSS_PHASE_SOLVE failed");
|
|
|
|
ConvertAndCopyToHost(x_d_, x_h_.data(), solution);
|
|
|
|
int cudss_data_info;
|
|
CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
|
|
GetCudssDataInfo(cudss_data_info),
|
|
"cudssDataGet with CUDSS_DATA_INFO failed");
|
|
const auto solve_status = static_cast<cudssStatus_t>(cudss_data_info);
|
|
|
|
if (solve_status != CUDSS_STATUS_SUCCESS) {
|
|
return LinearSolverTerminationType::FAILURE;
|
|
}
|
|
|
|
return LinearSolverTerminationType::SUCCESS;
|
|
}
|
|
|
|
private:
|
|
cudssStatus_t GetCudssDataInfo(int& cudss_data_info) {
|
|
CHECK_NE(cudss_context_.get(), nullptr);
|
|
std::size_t size_written = 0;
|
|
CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS(
|
|
cudssDataGet(context_->cudss_handle_,
|
|
cudss_context_->solver_data_,
|
|
CUDSS_DATA_INFO,
|
|
&cudss_data_info,
|
|
sizeof(cudss_data_info),
|
|
&size_written));
|
|
// TODO: enable following check after cudssDataGet will be fixed
|
|
// CHECK_EQ(size_written, sizeof(cudss_data_info));
|
|
|
|
return CUDSS_STATUS_SUCCESS;
|
|
}
|
|
|
|
LinearSolverTerminationType Analyze(const CompressedRowSparseMatrix* lhs,
|
|
std::string* message) {
|
|
if (auto status = SetupCudssMatrices(lhs, message);
|
|
status != LinearSolverTerminationType::SUCCESS) {
|
|
return status;
|
|
}
|
|
|
|
lhs_rows_d_.CopyFromCpu(lhs->rows(), lhs->num_rows() + 1);
|
|
lhs_cols_d_.CopyFromCpu(lhs->cols(), lhs->num_nonzeros());
|
|
|
|
// Analyze and factorization results are stored in cudssData_t (managed by
|
|
// CudssContext). Given that cuDSS 0.3.0 does not reset it's error state in
|
|
// case of failed numerics at factorization stage, we have to reset
|
|
// cudssData_t and to recompute an analyze stage while trying to factorize a
|
|
// rescaled matrix with the same structure.
|
|
// TODO: move creation of CudssContext to ctor of CudaSparseCholeskyImpl
|
|
cudss_context_ = std::make_unique<CudssContext>(context_->cudss_handle_);
|
|
|
|
CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
|
|
cudssExecute(context_->cudss_handle_,
|
|
CUDSS_PHASE_ANALYSIS,
|
|
cudss_context_->solver_config_,
|
|
cudss_context_->solver_data_,
|
|
cudss_lhs_.Get(),
|
|
cudss_x_.Get(),
|
|
cudss_rhs_.Get()),
|
|
"cudssExecute with CUDSS_PHASE_ANALYSIS failed");
|
|
|
|
return LinearSolverTerminationType::SUCCESS;
|
|
}
|
|
|
|
// Resize buffers and setup cuDSS structs that describe the type and storage
|
|
// configuration of linear system operands.
|
|
LinearSolverTerminationType SetupCudssMatrices(
|
|
const CompressedRowSparseMatrix* lhs, std::string* message) {
|
|
const auto num_rows = lhs->num_rows();
|
|
const auto num_nonzeros = lhs->num_nonzeros();
|
|
|
|
if constexpr (std::is_same_v<Scalar, float>) {
|
|
lhs_values_h_.Reserve(num_nonzeros);
|
|
rhs_h_.Reserve(num_rows);
|
|
x_h_.Reserve(num_rows);
|
|
}
|
|
|
|
lhs_rows_d_.Reserve(num_rows + 1);
|
|
lhs_cols_d_.Reserve(num_nonzeros);
|
|
lhs_values_d_.Reserve(num_nonzeros);
|
|
rhs_d_.Reserve(num_rows);
|
|
x_d_.Reserve(num_rows);
|
|
|
|
static constexpr auto kFailedToCreateCuDSSMatrix =
|
|
"cudssMatrixCreate() call failed";
|
|
CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(cudss_lhs_.Reset(num_rows,
|
|
num_rows,
|
|
num_nonzeros,
|
|
lhs_rows_d_.data(),
|
|
nullptr,
|
|
lhs_cols_d_.data(),
|
|
lhs_values_d_.data(),
|
|
CUDA_R_32I,
|
|
kCuDSSScalar,
|
|
CUDSS_MTYPE_SPD,
|
|
CUDSS_MVIEW_LOWER,
|
|
CUDSS_BASE_ZERO),
|
|
kFailedToCreateCuDSSMatrix);
|
|
|
|
CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
|
|
cudss_rhs_.Reset(num_rows,
|
|
1,
|
|
num_rows,
|
|
rhs_d_.data(),
|
|
kCuDSSScalar,
|
|
CUDSS_LAYOUT_COL_MAJOR),
|
|
kFailedToCreateCuDSSMatrix);
|
|
|
|
CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
|
|
cudss_x_.Reset(num_rows,
|
|
1,
|
|
num_rows,
|
|
x_d_.data(),
|
|
kCuDSSScalar,
|
|
CUDSS_LAYOUT_COL_MAJOR),
|
|
kFailedToCreateCuDSSMatrix);
|
|
|
|
return LinearSolverTerminationType::SUCCESS;
|
|
}
|
|
|
|
template <typename S, typename D>
|
|
void Convert(const S* source, D* destination, size_t size) {
|
|
Eigen::Map<Eigen::Matrix<D, Eigen::Dynamic, 1>>(destination, size) =
|
|
Eigen::Map<const Eigen::Matrix<S, Eigen::Dynamic, 1>>(source, size)
|
|
.template cast<D>();
|
|
}
|
|
|
|
void ConvertAndCopyToDevice(const double* source,
|
|
Scalar* intermediate,
|
|
CudaBuffer<Scalar>& destination) {
|
|
const auto size = destination.size();
|
|
if constexpr (std::is_same_v<Scalar, double>) {
|
|
destination.CopyFromCpu(source, size);
|
|
} else {
|
|
Convert(source, intermediate, size);
|
|
destination.CopyFromCpu(intermediate, size);
|
|
}
|
|
}
|
|
|
|
void ConvertAndCopyToHost(const CudaBuffer<Scalar>& source,
|
|
Scalar* intermediate,
|
|
double* destination) {
|
|
const auto size = source.size();
|
|
if constexpr (std::is_same_v<Scalar, double>) {
|
|
source.CopyToCpu(destination, source.size());
|
|
} else {
|
|
source.CopyToCpu(intermediate, source.size());
|
|
Convert(intermediate, destination, size);
|
|
}
|
|
}
|
|
|
|
ContextImpl* context_{nullptr};
|
|
std::unique_ptr<CudssContext> cudss_context_;
|
|
CuDSSMatrixCSR cudss_lhs_;
|
|
CuDSSMatrixDense cudss_rhs_;
|
|
CuDSSMatrixDense cudss_x_;
|
|
|
|
CudaPinnedHostBuffer<Scalar> lhs_values_h_;
|
|
CudaPinnedHostBuffer<Scalar> rhs_h_;
|
|
CudaPinnedHostBuffer<Scalar> x_h_;
|
|
CudaBuffer<int> lhs_rows_d_;
|
|
CudaBuffer<int> lhs_cols_d_;
|
|
CudaBuffer<Scalar> lhs_values_d_;
|
|
CudaBuffer<Scalar> rhs_d_;
|
|
CudaBuffer<Scalar> x_d_;
|
|
|
|
LinearSolverTerminationType analyze_result_ =
|
|
LinearSolverTerminationType::FATAL_ERROR;
|
|
LinearSolverTerminationType factorize_result_ =
|
|
LinearSolverTerminationType::FATAL_ERROR;
|
|
};
|
|
|
|
template <typename Scalar>
|
|
std::unique_ptr<SparseCholesky> CudaSparseCholesky<Scalar>::Create(
|
|
ContextImpl* context, const OrderingType ordering_type) {
|
|
if (ordering_type == OrderingType::NESDIS) {
|
|
LOG(FATAL)
|
|
<< "Congratulations you have found a bug in Ceres Solver. Please "
|
|
"report it to the Ceres Solver developers.";
|
|
return nullptr;
|
|
}
|
|
|
|
if (context == nullptr || !context->IsCudaInitialized()) {
|
|
LOG(FATAL) << "CudaSparseCholesky requires CUDA context to be initialized";
|
|
return nullptr;
|
|
}
|
|
|
|
return std::make_unique<CudaSparseCholeskyImpl<Scalar>>(context);
|
|
}
|
|
|
|
template class CudaSparseCholesky<float>;
|
|
template class CudaSparseCholesky<double>;
|
|
|
|
} // namespace ceres::internal
|
|
|
|
#undef CUDSS_STATUS_CHECK
|
|
#undef CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR
|
|
#undef CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS
|
|
|
|
#endif // CERES_NO_CUDSS
|