Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
linalg/backends/cublas/detail/gemm.hpp
Go to the documentation of this file.
1#pragma once
2
14
15#include <concepts>
16#include <cstddef>
17#include <limits>
18#include <memory>
19#include <type_traits>
20#include <utility>
21
22namespace uni20::linalg::detail::cublas_backend
23{
24
25template <uni20::cublas::CublasScalar Scalar> struct GemmPlan
26{
27 blas::BlasWritableMatrix<Scalar, uni20::cuda::CudaBufferView<Scalar>> output{};
28 blas::BlasReadableMatrix<Scalar, uni20::cuda::CudaBufferView<Scalar const>> lhs{};
29 blas::BlasReadableMatrix<Scalar, uni20::cuda::CudaBufferView<Scalar const>> rhs{};
30 blas_int inner = 0;
31 int device = -1;
32 bool has_work = false;
33
34 [[nodiscard]] uni20::cuda::DeviceResources& resources() const
35 {
36 CHECK(has_work);
37 return output.data.buffer().resources();
38 }
39};
40
41template <uni20::cublas::CublasScalar Scalar> struct GemmPreparation
42{
44 GemmPlan<Scalar> plan{};
45};
46
47template <class Accessor, class Scalar> struct IsCudaAccessorFor : std::false_type
48{};
49
50template <class ElementType, class Scalar>
51struct IsCudaAccessorFor<uni20::cuda::CudaPointerAccessor<ElementType>, Scalar>
52 : std::bool_constant<std::same_as<std::remove_cv_t<ElementType>, Scalar>>
53{};
54
55template <class Accessor, class Scalar>
56struct IsCudaAccessorFor<uni20::conjugated_accessor<Accessor>, Scalar> : IsCudaAccessorFor<Accessor, Scalar>
57{};
58
59template <class Accessor, class Scalar>
60inline constexpr bool cuda_accessor_for = IsCudaAccessorFor<std::remove_cvref_t<Accessor>, Scalar>::value;
61
62template <class Handle, class Scalar> struct IsCudaBufferViewFor : std::false_type
63{};
64
65template <class ElementType, class Scalar>
66struct IsCudaBufferViewFor<uni20::cuda::CudaBufferView<ElementType>, Scalar>
67 : std::bool_constant<std::same_as<std::remove_cv_t<ElementType>, Scalar>>
68{};
69
70template <class Handle, class Scalar>
71inline constexpr bool is_cuda_buffer_view_for = IsCudaBufferViewFor<std::remove_cvref_t<Handle>, Scalar>::value;
72
73template <class Mdspan, class Scalar>
74concept readable_cuda_mdspan_for =
76 std::same_as<std::remove_cv_t<typename std::remove_cvref_t<Mdspan>::element_type>, Scalar> &&
77 cuda_accessor_for<typename std::remove_cvref_t<Mdspan>::accessor_type, Scalar> &&
78 is_cuda_buffer_view_for<blas::detail::span_data_t<Mdspan>, Scalar>;
79
80template <class Mdspan, class Scalar>
81concept writable_cuda_mdspan_for =
83 std::same_as<typename std::remove_cvref_t<Mdspan>::element_type, Scalar> &&
84 std::same_as<typename std::remove_cvref_t<Mdspan>::accessor_type, uni20::cuda::CudaPointerAccessor<Scalar>> &&
85 is_cuda_buffer_view_for<blas::detail::span_data_t<Mdspan>, Scalar>;
86
87template <class Scalar, class OutputMdspan, class LhsMdspan, class RhsMdspan>
88concept GemmMdspans =
89 uni20::cublas::CublasScalar<Scalar> && writable_cuda_mdspan_for<std::remove_cvref_t<OutputMdspan>, Scalar> &&
90 readable_cuda_mdspan_for<std::remove_cvref_t<LhsMdspan>, Scalar> &&
91 readable_cuda_mdspan_for<std::remove_cvref_t<RhsMdspan>, Scalar>;
92
94template <class Scalar, class OutputMdspan, class LhsMdspan, class RhsMdspan> consteval bool accepts_gemm_types()
95{
96 return GemmMdspans<Scalar, OutputMdspan, LhsMdspan, RhsMdspan>;
97}
98
99template <class Scalar, class Handle> std::size_t required_elements(blas::BlasWritableMatrix<Scalar, Handle> matrix)
100{
101 CHECK(matrix.rows >= 0 && matrix.cols >= 0 && matrix.leading_dimension > 0, matrix.rows, matrix.cols,
102 matrix.leading_dimension);
103 if (matrix.rows == 0 || matrix.cols == 0) return 0;
104
105 auto const rows = static_cast<std::size_t>(matrix.rows);
106 auto const cols = static_cast<std::size_t>(matrix.cols);
107 auto const leading_dimension = static_cast<std::size_t>(matrix.leading_dimension);
108 CHECK(cols - 1 <= (std::numeric_limits<std::size_t>::max() - rows) / leading_dimension, rows, cols,
109 leading_dimension);
110 return (cols - 1) * leading_dimension + rows;
111}
112
113template <class Scalar, class Handle> std::size_t required_elements(blas::BlasReadableMatrix<Scalar, Handle> matrix)
114{
115 return required_elements(blas::BlasWritableMatrix<Scalar, Handle>{
116 .data = matrix.data, .rows = matrix.rows, .cols = matrix.cols, .leading_dimension = matrix.leading_dimension});
117}
118
119template <class View, class Matrix> void require_view_covers_matrix(View const& view, Matrix const& matrix)
120{
121 auto const offset = view.element_offset();
122 auto const size = view.buffer().size();
123 auto const required = required_elements(matrix);
124 CHECK(offset <= size && required <= size - offset, offset, required, size);
125}
126
127template <class Pointer> Pointer offset_pointer(Pointer pointer, std::size_t offset)
128{
129 if (offset == 0) return pointer;
130 CHECK(pointer != nullptr, offset);
131 return pointer + offset;
132}
133
134template <class Scalar, class Handle>
135auto with_data(blas::BlasWritableMatrix<Scalar, Handle> matrix, Scalar* data) -> blas::BlasWritableMatrix<Scalar>
136{
137 return {.data = data, .rows = matrix.rows, .cols = matrix.cols, .leading_dimension = matrix.leading_dimension};
138}
139
140template <class Scalar, class Handle>
141auto with_data(blas::BlasReadableMatrix<Scalar, Handle> matrix, Scalar const* data) -> blas::BlasReadableMatrix<Scalar>
142{
143 return {.data = data,
144 .rows = matrix.rows,
145 .cols = matrix.cols,
146 .leading_dimension = matrix.leading_dimension,
147 .transform = matrix.transform};
148}
149
150template <uni20::cublas::CublasScalar Scalar, class Handle>
151auto readonly_matrix(blas::BlasReadableMatrix<Scalar, Handle> matrix)
152 -> blas::BlasReadableMatrix<Scalar, uni20::cuda::CudaBufferView<Scalar const>>
153{
154 return {.data = uni20::cuda::CudaBufferView<Scalar const>{matrix.data},
155 .rows = matrix.rows,
156 .cols = matrix.cols,
157 .leading_dimension = matrix.leading_dimension,
158 .transform = matrix.transform};
159}
160
161template <uni20::cublas::CublasScalar Scalar, class LhsHandle, class RhsHandle>
162GemmPreparation<Scalar>
163prepare_staged_gemm(blas::BlasWritableMatrix<Scalar, uni20::cuda::CudaBufferView<Scalar>> output,
164 blas::BlasReadableMatrix<Scalar, LhsHandle> lhs, blas::BlasReadableMatrix<Scalar, RhsHandle> rhs)
165{
166 if (!uni20::linalg::cublas::detail::provider_transforms_are_supported(lhs, rhs))
167 {
168 return {.attempt = KernelAttempt::unsupported_transform};
169 }
170
171 blas_int const inner = uni20::linalg::cublas::detail::require_gemm_shape(output, lhs, rhs);
172 if (output.rows == 0 || output.cols == 0)
173 {
174 return {.attempt = KernelAttempt::success};
175 }
176
177 auto& output_buffer = output.data.buffer();
178 auto const& lhs_buffer = lhs.data.buffer();
179 auto const& rhs_buffer = rhs.data.buffer();
180 CHECK(std::addressof(output_buffer) != std::addressof(lhs_buffer) &&
181 std::addressof(output_buffer) != std::addressof(rhs_buffer),
182 "cuBLAS GEMM output must not share a CUDA buffer with an input");
183
184 int const device = output_buffer.device().ordinal();
185 if (lhs_buffer.device().ordinal() != device || rhs_buffer.device().ordinal() != device)
186 {
187 return {.attempt = KernelAttempt::incompatible_devices};
188 }
189 require_view_covers_matrix(output.data, output);
190 require_view_covers_matrix(lhs.data, lhs);
191 require_view_covers_matrix(rhs.data, rhs);
192
193 return {.attempt = KernelAttempt::success,
194 .plan = {.output = output,
195 .lhs = readonly_matrix(lhs),
196 .rhs = readonly_matrix(rhs),
197 .inner = inner,
198 .device = device,
199 .has_work = true}};
200}
201
202template <uni20::cublas::CublasScalar Scalar, class OutputMdspan, class LhsMdspan, class RhsMdspan>
203 requires GemmMdspans<Scalar, OutputMdspan, LhsMdspan, RhsMdspan>
204GemmPreparation<Scalar> prepare_gemm(OutputMdspan&& output, LhsMdspan&& lhs, RhsMdspan&& rhs)
205{
206 CHECK_EQUAL(lhs.extent(1), rhs.extent(0));
207 CHECK_EQUAL(output.extent(0), lhs.extent(0));
208 CHECK_EQUAL(output.extent(1), rhs.extent(1));
209
210 if (output.extent(0) == 0 || output.extent(1) == 0)
211 {
212 return {.attempt = KernelAttempt::success,
213 .plan = {.device = blas::detail::span_data(output).buffer().device().ordinal()}};
214 }
215
216 auto output_stage = blas::try_mdspan_matrix_stage(output);
217 auto lhs_stage = blas::try_mdspan_matrix_stage(lhs);
218 auto rhs_stage = blas::try_mdspan_matrix_stage(rhs);
219 if (!output_stage || !lhs_stage || !rhs_stage)
220 {
221 return {.attempt = KernelAttempt::unsupported_layout};
222 }
223 CHECK(!output_stage->needs_conjugation);
224
225 auto const output_matrix = blas::blas_writable_matrix(*output_stage);
226 if (output_stage->unit_stride_axis == 0)
227 {
228 return prepare_staged_gemm(output_matrix, blas::blas_readable_matrix(*lhs_stage),
229 blas::blas_readable_matrix(*rhs_stage));
230 }
231
232 auto lhs_matrix = blas::blas_readable_matrix(*rhs_stage);
233 auto rhs_matrix = blas::blas_readable_matrix(*lhs_stage);
234 lhs_matrix.transform = blas::compose(blas::MatrixTransform::transpose, lhs_matrix.transform);
235 rhs_matrix.transform = blas::compose(blas::MatrixTransform::transpose, rhs_matrix.transform);
236 return prepare_staged_gemm(output_matrix, lhs_matrix, rhs_matrix);
237}
238
239template <uni20::cublas::CublasScalar Scalar>
240void execute_gemm(uni20::cublas::ExecutionLease& execution, GemmPlan<Scalar> const& plan, Scalar alpha, Scalar beta)
241{
242 CHECK(plan.has_work);
243 CHECK_EQUAL(execution.handle().device(), plan.device, "cuBLAS execution lease must match the operand device");
244
245 auto& output_buffer = plan.output.data.buffer();
246 auto const& lhs_buffer = plan.lhs.data.buffer();
247 auto const& rhs_buffer = plan.rhs.data.buffer();
248 auto output_access = output_buffer.write_synchronized_with(execution.stream());
249 auto lhs_access = lhs_buffer.read_synchronized_with(execution.stream());
250 auto rhs_access = rhs_buffer.read_synchronized_with(execution.stream());
251
252 auto const raw_output =
253 with_data(plan.output, offset_pointer(output_access.data(), plan.output.data.element_offset()));
254 auto const raw_lhs = with_data(plan.lhs, offset_pointer(lhs_access.data(), plan.lhs.data.element_offset()));
255 auto const raw_rhs = with_data(plan.rhs, offset_pointer(rhs_access.data(), plan.rhs.data.element_offset()));
256 uni20::linalg::cublas::gemm(execution, raw_output, alpha, raw_lhs, raw_rhs, beta);
257
258 uni20::cuda::AccessCompletion completion(execution.stream());
259 completion.release(output_access);
260 completion.release(lhs_access);
261 completion.release(rhs_access);
262}
263
264template <uni20::cublas::CublasScalar Scalar, class OutputMdspan, class LhsMdspan, class RhsMdspan>
265 requires GemmMdspans<Scalar, OutputMdspan, LhsMdspan, RhsMdspan>
266KernelAttempt try_gemm(OutputMdspan&& output, Scalar alpha, LhsMdspan&& lhs, RhsMdspan&& rhs, Scalar beta)
267{
268 auto preparation = prepare_gemm<Scalar>(std::forward<OutputMdspan>(output), std::forward<LhsMdspan>(lhs),
269 std::forward<RhsMdspan>(rhs));
270 if (!kernel_attempt_succeeded(preparation.attempt) || !preparation.plan.has_work)
271 {
272 return preparation.attempt;
273 }
274
275 auto execution = uni20::cublas::execution_pool(preparation.plan.resources()).acquire();
276 execute_gemm(execution, preparation.plan, alpha, beta);
278}
279
280} // namespace uni20::linalg::detail::cublas_backend
Typed CUDA device buffers and scoped stream access guards.
Move-only dynamic pairing of one cuBLAS handle slot and one idle stream.
Definition cublas/execution.hpp:56
cuda::Stream const & stream() const noexcept
Access the dynamically selected CUDA stream.
Definition cublas/execution.hpp:77
HandleSlot & handle() const
Access the exclusively leased cuBLAS handle slot.
Definition cublas/execution.hpp:74
ExecutionLease acquire()
Block until a handle and then an idle stream are available.
Definition cublas/execution.cpp:203
int device() const noexcept
Return the device on which this handle was created.
Definition cublas/execution.hpp:38
One operation-tail completion shared by multiple CUDA buffer accesses.
Definition buffer.hpp:1160
Non-owning opaque view into one logical CUDA buffer.
Definition cuda_storage.hpp:33
Mutable stream and provider resources associated with one CUDA device.
Definition runtime.hpp:222
Mutable strided mdspec-like type with a specified static rank.
Definition mdspan/concepts.hpp:524
A strided mdspec-like type with a specified static rank.
Definition mdspan/concepts.hpp:516
Scalar types supported by the cuBLAS GEMM wrappers.
Definition backend/cublas/gemm.hpp:27
CUDA Tensor storage with deferred device-memory data descriptors.
locale_t value
Definition data_table.cpp:49
Allocation-free runtime results for kernel backend attempts.
Checked provider-ready matrix wrappers over cuBLAS GEMM.
Lower mdspan matrix views to provider-ready BLAS operands.
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
ExecutionPool & execution_pool(cuda::DeviceResources &resources)
Return the lazily constructed cuBLAS execution pool for one CUDA device.
Definition cublas/execution.cpp:215
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
constexpr auto blas_readable_matrix(MdspanMatrixStage< Scalar, Handle > const &stage) -> BlasReadableMatrix< Scalar, Handle >
Return the provider-ready readable operand for a staged mdspan matrix.
Definition mdspan_matrix.hpp:238
constexpr MatrixTransform compose(MatrixTransform outer, MatrixTransform inner)
Compose two matrix transforms as outer(inner(A)).
Definition blas_matrix.hpp:58
void gemm(uni20::cublas::ExecutionLease &execution, blas::BlasWritableMatrix< Scalar, OutputHandle > output, Scalar alpha, blas::BlasReadableMatrix< Scalar, LhsHandle > lhs, blas::BlasReadableMatrix< Scalar, RhsHandle > rhs, Scalar beta)
Run provider-ready column-major GEMM through cuBLAS.
Definition linalg/cublas/gemm.hpp:58
constexpr bool kernel_attempt_succeeded(KernelAttempt attempt) noexcept
Return whether a backend performed the operation.
Definition kernel_attempt.hpp:40
KernelAttempt
Result of one runtime kernel backend attempt.
Definition kernel_attempt.hpp:27
Definition async/async.hpp:26
mpreal beta(mpreal const &a, mpreal const &x)
Real Beta function; positive rational/integer pairs retain exact values.
Definition mpreal_special.hpp:164
Accessor for a CUDA mdspan whose device pointer has been leased.
Definition cuda_accessor.hpp:80
#define CHECK(cond,...)
Definition trace.hpp:299
#define CHECK_EQUAL(a, b,...)
Definition trace.hpp:309