30namespace detail::cusolver_backend
33template <
class Scalar>
34concept CusolverSvdScalar = std::same_as<Scalar, float> || std::same_as<Scalar, double>;
36template <
class Mdspec,
class Scalar, std::
size_t Rank>
37concept RawWritableCudaMdspec =
39 std::same_as<typename std::remove_cvref_t<Mdspec>::element_type, Scalar> &&
42template <
class Values,
class Left,
class Right,
class Matrix>
43concept CusolverSvdMdspecs = CusolverSvdScalar<typename std::remove_cvref_t<Matrix>::element_type> &&
44 RawWritableCudaMdspec<Values, typename std::remove_cvref_t<Matrix>::element_type, 1> &&
45 RawWritableCudaMdspec<Left, typename std::remove_cvref_t<Matrix>::element_type, 2> &&
46 RawWritableCudaMdspec<Right, typename std::remove_cvref_t<Matrix>::element_type, 2> &&
47 RawWritableCudaMdspec<Matrix, typename std::remove_cvref_t<Matrix>::element_type, 2>;
49template <
class Scalar>
struct MatrixDescriptor
51 cuda::CudaBufferView<Scalar> data;
54 int leading_dimension = 0;
57template <CusolverSvdScalar Scalar>
struct SvdPlan
60 MatrixDescriptor<Scalar>
left;
61 MatrixDescriptor<Scalar> right_adjoint;
62 MatrixDescriptor<Scalar> matrix;
69template <
class Mdspec> [[nodiscard]]
auto try_matrix_descriptor(Mdspec&
span)
71 using scalar_type =
typename std::remove_cvref_t<Mdspec>::element_type;
73 if (!stage || stage->unit_stride_axis != 0 || stage->needs_conjugation)
74 return std::optional<MatrixDescriptor<scalar_type>>{};
76 if (!std::in_range<int>(matrix.rows) || !std::in_range<int>(matrix.cols) ||
77 !std::in_range<int>(matrix.leading_dimension))
78 return std::optional<MatrixDescriptor<scalar_type>>{};
79 return std::optional{MatrixDescriptor<scalar_type>{.data = matrix.data,
80 .rows =
static_cast<int>(matrix.rows),
81 .columns =
static_cast<int>(matrix.cols),
82 .leading_dimension =
static_cast<int>(matrix.leading_dimension)}};
85template <
class Scalar> [[nodiscard]] std::size_t required_elements(MatrixDescriptor<Scalar>
const& matrix)
87 if (matrix.rows == 0 || matrix.columns == 0)
return 0;
88 auto const rows =
static_cast<std::size_t
>(matrix.rows);
89 auto const columns =
static_cast<std::size_t
>(matrix.columns);
90 auto const leading_dimension =
static_cast<std::size_t
>(matrix.leading_dimension);
91 CHECK(columns - 1 <= (std::numeric_limits<std::size_t>::max() - rows) / leading_dimension);
92 return (columns - 1) * leading_dimension +
rows;
95template <
class View>
void require_descriptor_range(View
const& view, std::size_t required)
97 auto const offset = view.element_offset();
98 auto const size = view.buffer().size();
99 CHECK(offset <= size && required <= size - offset, offset, required, size);
102template <
class Po
inter> [[nodiscard]] Pointer offset_pointer(Pointer pointer, std::size_t offset)
104 if (offset == 0)
return pointer;
105 CHECK(pointer !=
nullptr, offset);
106 return pointer + offset;
109template <CusolverSvdScalar Scalar>
int query_workspace(cusolver::ExecutionLease& execution,
int rows,
int columns)
112 if constexpr (std::same_as<Scalar, float>)
113 cusolver::check(cusolverDnSgesvd_bufferSize(execution.handle().native_handle(), rows, columns, &size),
114 "cusolverDnSgesvd_bufferSize", execution.handle().device());
116 cusolver::check(cusolverDnDgesvd_bufferSize(execution.handle().native_handle(), rows, columns, &size),
117 "cusolverDnDgesvd_bufferSize", execution.handle().device());
118 CHECK(size >= 0, size);
122template <
class T>
class StreamAllocation {
124 StreamAllocation(cuda::Stream
const& stream, std::size_t size,
bool memory_pools_supported)
125 : stream_(&
stream), stream_ordered_(memory_pools_supported)
127 if (size == 0)
return;
128 CHECK(size <= std::numeric_limits<std::size_t>::max() /
sizeof(T), size,
sizeof(T));
129 cuda::ScopedDevice guard(
stream.device());
131 cuda::check(cudaMallocAsync(
reinterpret_cast<void**
>(&data_), size *
sizeof(T),
stream.native_handle()),
132 "cudaMallocAsync cuSOLVER workspace",
stream.device());
134 cuda::check(cudaMalloc(
reinterpret_cast<void**
>(&data_), size *
sizeof(T)),
"cudaMalloc cuSOLVER workspace",
138 StreamAllocation(StreamAllocation
const&) =
delete;
139 StreamAllocation& operator=(StreamAllocation
const&) =
delete;
140 ~StreamAllocation() noexcept
142 if (data_ ==
nullptr)
return;
145 cuda::ScopedDevice guard(stream_->device());
146 auto const status = stream_ordered_ ? cudaFreeAsync(data_, stream_->native_handle()) : cudaFree(data_);
147 if (status != cudaSuccess)
148 PANIC(
"cuSOLVER workspace cleanup failed", stream_->device(), cudaGetErrorName(status),
149 cudaGetErrorString(status));
153 PANIC(
"CUDA device selection failed while freeing cuSOLVER workspace", stream_->device());
157 [[nodiscard]] T* data() const noexcept {
return data_; }
160 cuda::Stream
const* stream_ =
nullptr;
162 bool stream_ordered_ =
false;
165template <CusolverSvdScalar Scalar>
166void invoke_gesvd(cusolver::ExecutionLease& execution, SvdPlan<Scalar>
const& plan, Scalar* matrix,
167 Scalar*
singular_values, Scalar* left, Scalar* right_adjoint, Scalar* workspace,
int workspace_size,
170 signed char const jobu =
static_cast<signed char>(plan.left_extent ==
SvdVectorExtent::Full ?
'A' :
'S');
171 signed char const jobvt =
static_cast<signed char>(plan.right_extent ==
SvdVectorExtent::Full ?
'A' :
'S');
172 auto handle = execution.handle().native_handle();
173 if constexpr (std::same_as<Scalar, float>)
176 plan.matrix.leading_dimension,
singular_values, left, plan.left.leading_dimension,
177 right_adjoint, plan.right_adjoint.leading_dimension, workspace, workspace_size,
179 "cusolverDnSgesvd", plan.device);
184 plan.matrix.leading_dimension,
singular_values, left, plan.left.leading_dimension,
185 right_adjoint, plan.right_adjoint.leading_dimension, workspace, workspace_size,
187 "cusolverDnDgesvd", plan.device);
191template <CusolverSvdScalar Scalar>
KernelAttempt execute_svd(SvdPlan<Scalar>
const& plan)
193 auto& resources = plan.matrix.data.buffer().resources();
195 int const workspace_size = query_workspace<Scalar>(execution, plan.matrix.rows, plan.matrix.columns);
196 bool const memory_pools_supported = resources.device().capabilities().memory_pools_supported;
197 StreamAllocation<Scalar> workspace(execution.stream(),
static_cast<std::size_t
>(workspace_size),
198 memory_pools_supported);
199 StreamAllocation<int> device_info(execution.stream(), 1, memory_pools_supported);
201 auto values_access = plan.singular_values.buffer().write_synchronized_with(execution.stream());
202 auto left_access = plan.left.data.buffer().write_synchronized_with(execution.stream());
203 auto right_access = plan.right_adjoint.data.buffer().write_synchronized_with(execution.stream());
204 auto matrix_access = plan.matrix.data.buffer().write_synchronized_with(execution.stream());
206 auto*
values = offset_pointer(values_access.data(), plan.singular_values.element_offset());
207 auto*
left = offset_pointer(left_access.data(), plan.left.data.element_offset());
208 auto*
right = offset_pointer(right_access.data(), plan.right_adjoint.data.element_offset());
209 auto* matrix = offset_pointer(matrix_access.data(), plan.matrix.data.element_offset());
210 invoke_gesvd(execution, plan, matrix, values, left, right, workspace.data(), workspace_size, device_info.data());
213 cuda::check(cudaMemcpyAsync(&info, device_info.data(),
sizeof(info), cudaMemcpyDeviceToHost,
214 execution.stream().native_handle()),
215 "cudaMemcpyAsync cuSOLVER devInfo", plan.device);
216 execution.stream().synchronize();
217 matrix_access.release_after_synchronization();
218 right_access.release_after_synchronization();
219 left_access.release_after_synchronization();
220 values_access.release_after_synchronization();
221 ERROR_IF(info < 0,
"cuSOLVER gesvd reported an invalid parameter", -info);
222 ERROR_IF(info > 0,
"cuSOLVER gesvd failed to converge", info);
226template <
class Values,
class Left,
class Right,
class Matrix>
227 requires CusolverSvdMdspecs<Values, Left, Right, Matrix>
228KernelAttempt try_svd(svd_op
const& operation, Values& values,
Left& left,
Right& right_adjoint, Matrix& matrix)
230 using scalar_type =
typename std::remove_cvref_t<Matrix>::element_type;
235 auto matrix_descriptor = try_matrix_descriptor(matrix);
236 auto left_descriptor = try_matrix_descriptor(left);
237 auto right_descriptor = try_matrix_descriptor(right_adjoint);
241 int const rows = matrix_descriptor->rows;
242 int const columns = matrix_descriptor->columns;
243 int const rank = std::min(rows, columns);
248 CHECK_EQUAL(left_descriptor->columns, left_columns);
252 auto values_view =
values.data_descriptor();
253 int const device = matrix_descriptor->data.buffer().device().ordinal();
254 if (values_view.buffer().device().ordinal() != device ||
255 left_descriptor->data.buffer().device().ordinal() != device ||
256 right_descriptor->data.buffer().device().ordinal() != device)
259 auto const* matrix_buffer = std::addressof(matrix_descriptor->data.buffer());
260 CHECK(matrix_buffer != std::addressof(values_view.buffer()) &&
261 matrix_buffer != std::addressof(left_descriptor->data.buffer()) &&
262 matrix_buffer != std::addressof(right_descriptor->data.buffer()),
263 "cuSOLVER SVD matrix workspace must not alias an output buffer");
264 CHECK(std::addressof(values_view.buffer()) != std::addressof(left_descriptor->data.buffer()) &&
265 std::addressof(values_view.buffer()) != std::addressof(right_descriptor->data.buffer()) &&
266 std::addressof(left_descriptor->data.buffer()) != std::addressof(right_descriptor->data.buffer()),
267 "cuSOLVER SVD output buffers must be distinct");
269 require_descriptor_range(values_view,
static_cast<std::size_t
>(rank));
270 require_descriptor_range(matrix_descriptor->data, required_elements(*matrix_descriptor));
271 require_descriptor_range(left_descriptor->data, required_elements(*left_descriptor));
272 require_descriptor_range(right_descriptor->data, required_elements(*right_descriptor));
274 return execute_svd(SvdPlan<scalar_type>{.singular_values = values_view,
275 .left = *left_descriptor,
276 .right_adjoint = *right_descriptor,
277 .matrix = *matrix_descriptor,
279 .left_extent = operation.left,
280 .right_extent = operation.right,
287template <uni20::MutableRankedStr
idedMdspecLike<1> Values, uni20::MutableRankedStr
idedMdspecLike<2> Left,
288 uni20::MutableRankedStr
idedMdspecLike<2> Right, uni20::MutableRankedStr
idedMdspecLike<2> Matrix>
291 if constexpr (detail::cusolver_backend::CusolverSvdMdspecs<Values, Left, Right, Matrix>)
298template <uni20::MutableRankedStr
idedMdspecLike<1> Values, uni20::MutableRankedStr
idedMdspecLike<2> Left,
299 uni20::MutableRankedStr
idedMdspecLike<2> Right, uni20::MutableRankedStr
idedMdspecLike<2> Matrix>
300 requires detail::cusolver_backend::CusolverSvdMdspecs<Values, Left, Right, Matrix>
304 return detail::cusolver_backend::try_svd(operation, values, left, right, matrix);
Typed CUDA device buffers and scoped stream access guards.
ExecutionLease acquire()
Definition cusolver/execution.cpp:193
Mutable strided mdspec-like type with a specified static rank.
Definition mdspan/concepts.hpp:524
CUDA-accessible mdspec backed by a CudaBufferView descriptor.
Definition cuda_storage.hpp:118
cuda::ResourceLease< HandleSlot > handle
Definition cublas/execution.cpp:33
CUDA-device-callable accessors for leased Tensor storage.
Structured CUDA runtime failures and checked-call helpers.
CUDA Tensor storage with deferred device-memory data descriptors.
cuSOLVER handle slots and dynamically paired stream execution leases.
Structured cuSOLVER failures and checked-call helpers.
Operation-tag backend dispatch over tensor-view operands.
Lower mdspan matrix views to provider-ready BLAS operands.
int columns(std::FILE *stream)
Return the terminal column count associated with a C stream.
Definition terminal.cpp:493
std::pair< int, int > size(std::FILE *stream)
Return the terminal size associated with a C stream.
Definition terminal.cpp:504
int rows(std::FILE *stream)
Return the terminal row count associated with a C stream.
Definition terminal.cpp:482
void check(cudaError_t status, std::string_view operation, std::optional< int > device, std::source_location where)
Raise when a CUDA runtime operation did not return cudaSuccess.
Definition cuda_error.cpp:44
void check(cusolverStatus_t status, std::string_view operation, int device, std::source_location where)
Raise unless a cuSOLVER call returned CUSOLVER_STATUS_SUCCESS.
Definition cusolver_error.cpp:59
ExecutionPool & execution_pool(cuda::DeviceResources &resources)
Return the lazily constructed cuSOLVER execution pool for one device.
Definition cusolver/execution.cpp:205
void info(checked_format_string< Args... > format, Args &&... args)
Emit an informational display message.
Definition display.hpp:393
stream
Selects the ordinary display stream for a message.
Definition display.hpp:25
void status(presentation::semantic_glyph glyph, checked_format_string< Args... > format, Args &&... args)
Emit a semantic status message through the active display sink.
Definition display.hpp:388
constexpr auto blas_writable_matrix(MdspanMatrixStage< Scalar, Handle > const &stage) -> BlasWritableMatrix< Scalar, Handle >
Return the provider-ready writable operand for a staged mdspan matrix.
Definition mdspan_matrix.hpp:225
auto try_mdspan_matrix_stage(Mdspan const &span) -> std::optional< MdspanMatrixStage< std::remove_cv_t< typename Mdspan::element_type >, detail::span_data_t< Mdspan > > >
Build a matrix staging descriptor when direct provider lowering is possible.
Definition mdspan_matrix.hpp:202
blas_int workspace_size(Scalar const &query)
Convert a provider workspace query result to the configured LAPACK integer type.
Definition linalg/backends/lapack/common.hpp:43
auto values(Tensor &tensor)
Definition packed_matrix.hpp:29
Definition linalg/async.hpp:28
KernelAttempt try_kernel(BlasBackend, gemm_op const &, C &c, S const &alpha, A &a, B &b, S const &beta)
Pack logical host values and execute GEMM through value-preserving provider conversion.
Definition linalg/backends/blas/binary80.hpp:30
KernelAttempt
Result of one runtime kernel backend attempt.
Definition kernel_attempt.hpp:27
constexpr KernelAcceptance< KernelTypeAcceptance::no > kernel_types_no
Report that a backend rejects the argument types.
Definition dispatch_error.hpp:34
SvdVectorExtent
Extent policy for one side of an exact singular value decomposition.
Definition operation_tags.hpp:248
consteval auto kernel_accepts_types(BlasBackend, gemm_op const &, C &, S const &, A &, B &, S const &)
Accept host-readable native binary80 operands, including transforming accessors.
Definition linalg/backends/blas/binary80.hpp:19
constexpr KernelAcceptance< KernelTypeAcceptance::maybe > kernel_types_maybe
Report that runtime operand values determine backend acceptance.
Definition dispatch_error.hpp:37
auto singular_values(BackendSelector selector, async::Async< MatrixTensor > const &matrix, std::optional< Precision > requested={})
Schedule preserving exact singular values through an explicit selector.
Definition async/svd.hpp:416
std::size_t span
Definition presentation.cpp:2017
Accessor for a CUDA mdspan whose device pointer has been leased.
Definition cuda_accessor.hpp:80
Backend value for cuSOLVER dense factorization kernels.
Definition backend_selector.hpp:41
Exact dense singular value decomposition operation tag.
Definition operation_tags.hpp:292
#define CHECK(cond,...)
Definition trace.hpp:299
#define PANIC(...)
Definition trace.hpp:382
#define CHECK_EQUAL(a, b,...)
Definition trace.hpp:309
#define ERROR_IF(cond,...)
Definition trace.hpp:389