Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
cuda_storage.hpp
Go to the documentation of this file.
1#pragma once
2
15
16#include <bit>
17#include <concepts>
18#include <cstddef>
19#include <cstdint>
20#include <span>
21#include <stdexcept>
22#include <type_traits>
23#include <vector>
24
25namespace uni20::cuda
26{
27
33template <class ElementType> class CudaBufferView {
34 public:
35 using element_type = ElementType;
36 using value_type = std::remove_const_t<element_type>;
38 using buffer_reference = std::conditional_t<std::is_const_v<element_type>, buffer_type const&, buffer_type&>;
39
40 constexpr CudaBufferView() noexcept = default;
41
43 explicit constexpr CudaBufferView(buffer_reference buffer) noexcept : buffer_(&buffer) {}
44
46 template <class OtherElement>
47 requires(std::is_const_v<element_type> && !std::is_const_v<OtherElement> &&
48 std::same_as<std::remove_const_t<OtherElement>, value_type>)
50 : buffer_(other.buffer_), offset_(other.offset_)
51 {}
52
54 [[nodiscard]] explicit constexpr operator bool() const noexcept { return buffer_ != nullptr; }
55
57 [[nodiscard]] auto buffer() const -> buffer_reference
58 {
59 CHECK(buffer_ != nullptr, "cannot resolve an empty CUDA buffer view");
60 return *buffer_;
61 }
62
64 [[nodiscard]] constexpr std::size_t element_offset() const noexcept { return offset_; }
65
68 [[nodiscard]] constexpr CudaBufferView offset_by(std::size_t offset) const noexcept
69 {
70 CudaBufferView result = *this;
71 result.offset_ += offset;
72 return result;
73 }
74
75 friend constexpr bool operator==(CudaBufferView const&, CudaBufferView const&) = default;
76
77 private:
78 using buffer_pointer = std::add_pointer_t<std::remove_reference_t<buffer_reference>>;
79
80 buffer_pointer buffer_ = nullptr;
81 std::size_t offset_ = 0;
82
83 template <class> friend class CudaBufferView;
84};
85
86} // namespace uni20::cuda
87
88namespace uni20::cuda
89{
90
91namespace detail
92{
93
94// These representations have a numerical zero consisting entirely of zero
95// bytes. Do not infer that property from a user-specialized scalar trait.
96template <class T>
97inline constexpr bool has_zero_object_representation_v = [] {
98#if UNI20_HAS_FLOAT128
99 if constexpr (std::same_as<T, uni20::float128>) return true;
100#endif
101 return std::is_arithmetic_v<T>;
102}();
103
104template <class Real>
105inline constexpr bool has_zero_object_representation_v<uni20::detail::standard_complex<Real>> =
106 has_zero_object_representation_v<Real>;
107
108template <class Descriptor> struct IsCudaBufferView : std::false_type
109{};
110
111template <class ElementType> struct IsCudaBufferView<CudaBufferView<ElementType>> : std::true_type
112{};
113
114} // namespace detail
115
117template <class Span>
119 typename std::remove_cvref_t<Span>::data_descriptor_type;
120} && detail::IsCudaBufferView<typename std::remove_cvref_t<Span>::data_descriptor_type>::value;
121
122} // namespace uni20::cuda
123
124namespace uni20::cuda
125{
126
128struct CudaAccessorFactory
129{
130 template <class ElementType> using accessor_t = CudaPointerAccessor<ElementType>;
131 template <class ElementType> using device_accessor_t = CudaPointerAccessor<ElementType>;
132
133 template <class ElementType, class Storage>
134 [[nodiscard]] constexpr auto make_accessor(Storage const&) const noexcept -> accessor_t<ElementType>
135 {
136 return accessor_t<ElementType>{};
137 }
138
139 template <class ElementType, class Storage>
140 [[nodiscard]] constexpr auto make_device_accessor(Storage const&) const noexcept -> device_accessor_t<ElementType>
141 {
142 return device_accessor_t<ElementType>{};
143 }
144};
145
146} // namespace uni20::cuda
147
148namespace uni20
149{
150
164{
166 static constexpr std::size_t allocation_alignment = 256;
167
169 using accessor_factory_type = cuda::CudaAccessorFactory;
170#if UNI20_BACKEND_CUSOLVER && UNI20_BACKEND_CUBLAS
173#elif UNI20_BACKEND_CUSOLVER
175#elif UNI20_BACKEND_CUBLAS
177#else
179#endif
180
181 template <class ElementType> using storage_t = cuda::CudaBuffer<ElementType>;
182 template <class ElementType> using packed_storage_t = cuda::PartitionedCudaBuffer<ElementType>;
183
184 template <class ElementType>
185 [[nodiscard]] static auto
186 make_storage(std::size_t size,
188 {
189 return make_storage<ElementType>(cuda::device_resources(), size, initialization);
190 }
191
192 template <class ElementType>
193 [[nodiscard]] static auto
194 make_storage(context_type& context, std::size_t size,
196 {
197 storage_t<ElementType> result{context, size};
198 initialize_storage(result, initialization);
199 return result;
200 }
201
202 template <class ElementType>
203 [[nodiscard]] static auto
204 make_storage(cuda::Device device, std::size_t size,
206 {
207 return make_storage<ElementType>(cuda::device_resources(device.ordinal()), size, initialization);
208 }
209
210 template <class ElementType>
211 [[nodiscard]] static auto make_storage_like(
212 storage_t<ElementType> const& storage, std::size_t size,
214 {
215 return make_storage<ElementType>(storage.resources(), size, initialization);
216 }
217
218 template <class ElementType>
219 [[nodiscard]] static auto make_packed_storage(
220 std::size_t size, std::span<std::size_t const> offsets,
222 {
223 return make_packed_storage<ElementType>(cuda::device_resources(), size, offsets, initialization);
224 }
225
226 template <class ElementType>
227 [[nodiscard]] static auto make_packed_storage(
228 cuda::Device device, std::size_t size, std::span<std::size_t const> offsets,
230 {
231 return make_packed_storage<ElementType>(cuda::device_resources(device.ordinal()), size, offsets, initialization);
232 }
233
234 template <class ElementType>
235 [[nodiscard]] static auto make_packed_storage(
236 context_type& context, std::size_t size, std::span<std::size_t const> offsets,
238 {
239 std::vector<cuda::CudaBufferRange> ranges;
240 if (!offsets.empty())
241 {
242 CHECK_EQUAL(offsets.back(), size);
243 ranges.reserve(offsets.size() - 1);
244 for (std::size_t ordinal = 0; ordinal + 1 < offsets.size(); ++ordinal)
245 ranges.push_back(
246 cuda::CudaBufferRange{.offset = offsets[ordinal], .size = offsets[ordinal + 1] - offsets[ordinal]});
247 }
248 packed_storage_t<ElementType> result{context, size, ranges};
249 initialize_storage(result, initialization);
250 return result;
251 }
252
253 template <class ElementType>
254 [[nodiscard]] static auto make_packed_storage_like(
255 packed_storage_t<ElementType> const& storage, std::size_t size, std::span<std::size_t const> offsets,
257 {
258 return make_packed_storage<ElementType>(storage.resources(), size, offsets, initialization);
259 }
260
262 template <class ElementType>
263 [[nodiscard]] static auto allocation_context(packed_storage_t<ElementType> const& storage) noexcept -> context_type&
264 {
265 return storage.resources();
266 }
267
271 template <RealOrComplex ElementType>
272 static void initialize_packed_padding(packed_storage_t<ElementType>& storage, std::span<std::size_t const> offsets,
273 std::span<std::size_t const> block_ends)
274 {
275 CHECK_EQUAL(offsets.size(), block_ends.size() + 1);
276 bool has_padding = false;
277 for (std::size_t ordinal = 0; ordinal < block_ends.size(); ++ordinal)
278 {
279 CHECK(block_ends[ordinal] <= offsets[ordinal + 1], block_ends[ordinal], offsets[ordinal + 1]);
280 has_padding = has_padding || block_ends[ordinal] != offsets[ordinal + 1];
281 }
282 if (!has_padding) return;
283
284 auto stream = storage.resources().streams().acquire();
285 auto access = storage.write_synchronized_with(stream);
286 cuda::ScopedDevice guard(storage.device().ordinal());
287 for (std::size_t ordinal = 0; ordinal < block_ends.size(); ++ordinal)
288 {
289 auto const padding_size = offsets[ordinal + 1] - block_ends[ordinal];
290 if (padding_size == 0) continue;
291 cuda::check(cudaMemsetAsync(access.data() + block_ends[ordinal], 0, padding_size * sizeof(ElementType),
292 stream.native_handle()),
293 "zero packed CUDA BlockTensor padding", stream.device());
294 }
295 }
296
297 template <class ElementType>
298 [[nodiscard]] static bool storage_is_compatible(storage_t<ElementType> const& storage, cuda::Device device)
299 {
300 return storage.device() == device;
301 }
302
303 template <class ElementType>
304 [[nodiscard]] static auto
309
310 template <class ElementType>
311 [[nodiscard]] static auto
316
317 template <class ElementType>
318 [[nodiscard]] static auto
320 std::size_t ordinal) noexcept -> cuda::CudaBufferView<ElementType>
321 {
322 return cuda::CudaBufferView<ElementType>{storage.buffer(ordinal)};
323 }
324
325 template <class ElementType>
326 [[nodiscard]] static auto
328 std::size_t ordinal) noexcept -> cuda::CudaBufferView<ElementType const>
329 {
330 return cuda::CudaBufferView<ElementType const>{storage.buffer(ordinal)};
331 }
332
334 [[nodiscard]] static constexpr auto backend_selector() noexcept -> backend_selector_type
335 {
336#if UNI20_BACKEND_CUSOLVER && UNI20_BACKEND_CUBLAS
338#elif UNI20_BACKEND_CUSOLVER
340#elif UNI20_BACKEND_CUBLAS
342#else
344#endif
345 }
346
347 private:
348 template <class Buffer> static void initialize_storage(Buffer& storage, StorageInitialization initialization)
349 {
350 using element_type = typename Buffer::element_type;
351 if (storage.size() == 0) return;
352
353 if (initialization == StorageInitialization::Zero)
354 {
355 if constexpr (cuda::detail::has_zero_object_representation_v<element_type>)
356 {
357 auto stream = storage.resources().streams().acquire();
358 auto access = storage.write_synchronized_with(stream);
359 cuda::ScopedDevice guard(storage.device().ordinal());
360 cuda::check(cudaMemsetAsync(access.data(), 0, storage.size() * sizeof(element_type), stream.native_handle()),
361 "zero CUDA tensor storage", stream.device());
362 }
363 else
364 {
365 throw std::invalid_argument("CUDA zero initialization requires a supported numerical scalar representation");
366 }
367 return;
368 }
369
370#if UNI20_FILL_UNINITIALIZED_SNAN
371 if constexpr (RealOrComplex<element_type>)
372 {
373 using real_type = make_real_t<element_type>;
374 // These are the real precisions supported by the CUDA numerical
375 // backends. Repeat the representation for both complex components.
376 if constexpr (std::same_as<real_type, float> || std::same_as<real_type, double>)
377 {
378 using word_type = std::conditional_t<sizeof(real_type) == 4, std::uint32_t, std::uint64_t>;
379 auto const pattern = std::bit_cast<word_type>(detail::signaling_nan_value<real_type>());
380 auto stream = storage.resources().streams().acquire();
381 auto access = storage.write_synchronized_with(stream);
382 cuda::ScopedDevice guard(storage.device().ordinal());
383 cuda::detail::enqueue_initialization_pattern(access.data(), storage.size() * sizeof(element_type), pattern,
384 sizeof(real_type), stream.native_handle(), stream.device());
385 }
386 }
387#endif
388 }
389};
390
391} // namespace uni20
Stream-ordered diagnostic initialization of CUDA scalar storage.
Ordered backend selector values shared by tensor storage and linalg dispatch.
Typed CUDA device buffers and scoped stream access guards.
Non-owning opaque view into one logical CUDA buffer.
Definition cuda_storage.hpp:33
constexpr CudaBufferView offset_by(std::size_t offset) const noexcept
Return a view advanced by an element offset.
Definition cuda_storage.hpp:68
std::conditional_t< std::is_const_v< element_type >, buffer_type const &, buffer_type & > buffer_reference
Definition cuda_storage.hpp:38
constexpr CudaBufferView(CudaBufferView< OtherElement > other) noexcept
Convert a mutable view into a read-only view.
Definition cuda_storage.hpp:49
std::remove_const_t< element_type > value_type
Definition cuda_storage.hpp:36
constexpr std::size_t element_offset() const noexcept
Return this view's element offset from the logical buffer start.
Definition cuda_storage.hpp:64
friend constexpr bool operator==(CudaBufferView const &, CudaBufferView const &)=default
constexpr CudaBufferView() noexcept=default
auto buffer() const -> buffer_reference
Return the CUDA buffer whose completion ledger governs access.
Definition cuda_storage.hpp:57
ElementType element_type
Definition cuda_storage.hpp:35
Move-only logical CUDA buffer and completion state.
Definition buffer.hpp:222
Device device() const
Return the allocation's CUDA device.
Definition buffer.hpp:276
Mutable stream and provider resources associated with one CUDA device.
Definition runtime.hpp:222
StreamPool & streams() noexcept
Return the device's actually-idle stream pool.
Definition runtime.hpp:244
Validated CUDA device identity with cached immutable capabilities.
Definition device.hpp:105
constexpr int ordinal() const noexcept
Return this device's CUDA runtime ordinal.
Definition device.hpp:123
One CUDA allocation partitioned into independent logical buffers.
Definition buffer.hpp:709
Device device() const
Return the CUDA device containing the physical allocation.
Definition buffer.hpp:755
DeviceResources & resources() const
Return the device resources shared by every child buffer.
Definition buffer.hpp:748
auto write_synchronized_with(Stream const &stream) -> PartitionedWriteAccess< T >
Acquire write access to the complete contiguous allocation.
Definition buffer.hpp:1202
Temporarily select one CUDA device and restore the previous device on destruction.
Definition runtime.hpp:44
Stream acquire()
Block until one actually idle stream can be leased.
Definition runtime.cpp:594
Immediate or descriptor-backed mdspan metadata targeting CUDA access.
Definition mdspan/concepts.hpp:329
CUDA-accessible mdspec backed by a CudaBufferView descriptor.
Definition cuda_storage.hpp:118
CUDA-device-callable accessors for leased Tensor storage.
locale_t value
Definition data_table.cpp:49
Mdspan concept and accessor extensions for Uni20.
Definition cublas/execution.hpp:18
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
DeviceResources & device_resources()
Return canonical resources for the installed runtime's default device.
Definition runtime.cpp:820
stream
Selects the ordinary display stream for a message.
Definition display.hpp:25
Definition async/async.hpp:26
StorageInitialization
Numerical initialization requested from a storage implementation.
Definition common/initialization.hpp:26
Tensor storage policy for device-resident CUDA values.
Definition cuda_storage.hpp:164
cuda::CudaAccessorFactory accessor_factory_type
Definition cuda_storage.hpp:169
static auto make_storage(cuda::Device device, std::size_t size, StorageInitialization initialization=StorageInitialization::Uninitialized) -> storage_t< ElementType >
Definition cuda_storage.hpp:204
static auto make_packed_storage(std::size_t size, std::span< std::size_t const > offsets, StorageInitialization initialization=StorageInitialization::Uninitialized) -> packed_storage_t< ElementType >
Definition cuda_storage.hpp:219
static auto make_packed_data_descriptor(packed_storage_t< ElementType > &storage, std::size_t ordinal) noexcept -> cuda::CudaBufferView< ElementType >
Definition cuda_storage.hpp:319
static constexpr std::size_t allocation_alignment
Byte alignment guaranteed by CUDA runtime device allocations.
Definition cuda_storage.hpp:166
static auto make_packed_storage(context_type &context, std::size_t size, std::span< std::size_t const > offsets, StorageInitialization initialization=StorageInitialization::Uninitialized) -> packed_storage_t< ElementType >
Definition cuda_storage.hpp:235
static auto make_packed_storage_like(packed_storage_t< ElementType > const &storage, std::size_t size, std::span< std::size_t const > offsets, StorageInitialization initialization=StorageInitialization::Uninitialized) -> packed_storage_t< ElementType >
Definition cuda_storage.hpp:254
static bool storage_is_compatible(storage_t< ElementType > const &storage, cuda::Device device)
Definition cuda_storage.hpp:298
static auto make_packed_storage(cuda::Device device, std::size_t size, std::span< std::size_t const > offsets, StorageInitialization initialization=StorageInitialization::Uninitialized) -> packed_storage_t< ElementType >
Definition cuda_storage.hpp:227
static auto make_storage(std::size_t size, StorageInitialization initialization=StorageInitialization::Uninitialized) -> storage_t< ElementType >
Definition cuda_storage.hpp:186
static constexpr auto backend_selector() noexcept -> backend_selector_type
Return the ordered backend list for CUDA device storage.
Definition cuda_storage.hpp:334
static auto make_packed_data_descriptor(packed_storage_t< ElementType > const &storage, std::size_t ordinal) noexcept -> cuda::CudaBufferView< ElementType const >
Definition cuda_storage.hpp:327
static auto make_data_descriptor(storage_t< ElementType > const &storage) noexcept -> cuda::CudaBufferView< ElementType const >
Definition cuda_storage.hpp:312
static auto make_data_descriptor(storage_t< ElementType > &storage) noexcept -> cuda::CudaBufferView< ElementType >
Definition cuda_storage.hpp:305
static auto allocation_context(packed_storage_t< ElementType > const &storage) noexcept -> context_type &
Return the allocation context retained by a packed CUDA buffer.
Definition cuda_storage.hpp:263
static void initialize_packed_padding(packed_storage_t< ElementType > &storage, std::span< std::size_t const > offsets, std::span< std::size_t const > block_ends)
Initialize only the alignment gaps in a packed CUDA allocation.
Definition cuda_storage.hpp:272
static auto make_storage_like(storage_t< ElementType > const &storage, std::size_t size, StorageInitialization initialization=StorageInitialization::Uninitialized) -> storage_t< ElementType >
Definition cuda_storage.hpp:211
static auto make_storage(context_type &context, std::size_t size, StorageInitialization initialization=StorageInitialization::Uninitialized) -> storage_t< ElementType >
Definition cuda_storage.hpp:194
One logical range used to partition a CUDA allocation.
Definition buffer.hpp:42
Backend value for cuBLAS dense linalg kernels.
Definition backend_selector.hpp:34
Backend value for generic CUDA runtime kernels and transfers.
Definition backend_selector.hpp:48
Backend value for cuSOLVER dense factorization kernels.
Definition backend_selector.hpp:41
Ordered list of backend values tried by the dispatch walk.
Definition backend_selector.hpp:62
#define CHECK(cond,...)
Definition trace.hpp:299
#define CHECK_EQUAL(a, b,...)
Definition trace.hpp:309