Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
buffer.hpp
Go to the documentation of this file.
1#pragma once
2
13
14#include <algorithm>
15#include <atomic>
16#include <cstddef>
17#include <limits>
18#include <memory>
19#include <mutex>
20#include <span>
21#include <type_traits>
22#include <utility>
23#include <vector>
24
25namespace uni20::cuda
26{
27
28template <typename T = std::byte>
29 requires std::is_trivially_copyable_v<T>
30class CudaBuffer;
31template <typename T> class ReadAccess;
32template <typename T> class WriteAccess;
33template <typename T> class BlockingReadAccess;
34template <typename T> class BlockingWriteAccess;
35template <typename T> class OwningReadAccess;
36template <typename T> class PartitionedReadAccess;
37template <typename T> class PartitionedWriteAccess;
38class AccessCompletion;
39
42{
43 std::size_t offset = 0;
44 std::size_t size = 0;
45};
46
47namespace detail
48{
49
50[[noreturn]] inline void cuda_cleanup_failure(cudaError_t status, char const* operation, int device)
51{
52 PANIC("CUDA cleanup operation failed", operation, device, cudaGetErrorName(status), cudaGetErrorString(status));
53}
54
55inline void check_cuda_cleanup(cudaError_t status, char const* operation, int device) noexcept
56{
57 if (status != cudaSuccess)
58 {
59 cuda_cleanup_failure(status, operation, device);
60 }
61}
62
63class BufferCleanupDeviceGuard {
64 public:
65 explicit BufferCleanupDeviceGuard(int device) noexcept
66 {
67 check_cuda_cleanup(cudaGetDevice(&previous_device_), "cudaGetDevice", device);
68 restore_ = previous_device_ != device;
69 if (restore_)
70 {
71 check_cuda_cleanup(cudaSetDevice(device), "cudaSetDevice", device);
72 }
73 }
74
75 BufferCleanupDeviceGuard(BufferCleanupDeviceGuard const&) = delete;
76 BufferCleanupDeviceGuard& operator=(BufferCleanupDeviceGuard const&) = delete;
77
78 ~BufferCleanupDeviceGuard()
79 {
80 if (restore_)
81 {
82 check_cuda_cleanup(cudaSetDevice(previous_device_), "cudaSetDevice restore", previous_device_);
83 }
84 }
85
86 private:
87 int previous_device_ = -1;
88 bool restore_ = false;
89};
90
91inline void synchronize_after_failed_publication(Stream const& stream, char const* operation) noexcept
92{
93 try
94 {
95 stream.synchronize();
96 }
97 catch (...)
98 {
99 PANIC("CUDA buffer completion publication failed and stream synchronization also failed", operation,
100 stream.device());
101 }
102}
103
104inline void synchronize_after_failed_publication(cudaStream_t stream, int device, char const* operation) noexcept
105{
106 BufferCleanupDeviceGuard guard(device);
107 cudaError_t const status = cudaStreamSynchronize(stream);
108 if (status != cudaSuccess)
109 {
110 PANIC("CUDA buffer completion publication failed and stream synchronization also failed", operation, device,
111 cudaGetErrorName(status), cudaGetErrorString(status));
112 }
113}
114
115template <typename T> class CudaAllocation {
116 public:
117 CudaAllocation(DeviceResources& resources, std::size_t size) : resources_(&resources), size_(size)
118 {
119 CHECK(size <= std::numeric_limits<std::size_t>::max() / sizeof(T), size, sizeof(T));
120 std::size_t const bytes = size * sizeof(T);
121 if (size != 0)
122 {
123 if (resources.device().capabilities().memory_pools_supported)
124 this->allocate_stream_ordered(bytes);
125 else
126 this->allocate_blocking(bytes);
127 }
128 resources_->register_allocation();
129 registered_ = true;
130 }
131
132 CudaAllocation(CudaAllocation const&) = delete;
133 CudaAllocation& operator=(CudaAllocation const&) = delete;
134 ~CudaAllocation() { this->reset(); }
135
136 [[nodiscard]] T* data() noexcept { return data_; }
137 [[nodiscard]] T const* data() const noexcept { return data_; }
138 [[nodiscard]] std::size_t size() const noexcept { return size_; }
139 [[nodiscard]] DeviceResources& resources() const
140 {
141 CHECK(resources_ != nullptr);
142 return *resources_;
143 }
144 [[nodiscard]] Completion const& initial_completion() const noexcept { return initial_completion_; }
145 void require_blocking_deallocation() noexcept { force_blocking_deallocation_.store(true); }
146 void defer_release_after(Completion const& writer, std::span<Completion const> readers) noexcept
147 {
148 if (writer) resources_->defer_allocation_release_after(std::span(&writer, 1));
149 if (!readers.empty()) resources_->defer_allocation_release_after(readers);
150 }
151
152 private:
153 void allocate_blocking(std::size_t bytes)
154 {
155 void* raw_data = nullptr;
156 ScopedDevice guard(resources_->device().ordinal());
157 check(cudaMalloc(&raw_data, bytes), "cudaMalloc", resources_->device().ordinal());
158 data_ = static_cast<T*>(raw_data);
159 }
160
161 void allocate_stream_ordered(std::size_t bytes)
162 {
163 auto stream = resources_->streams().acquire();
164 void* raw_data = nullptr;
165 ScopedDevice guard(resources_->device().ordinal());
166 check(cudaMallocAsync(&raw_data, bytes, stream.native_handle()), "cudaMallocAsync",
167 resources_->device().ordinal());
168 data_ = static_cast<T*>(raw_data);
169
170 try
171 {
172 initial_completion_ = stream.record_completion();
173 }
174 catch (...)
175 {
176 synchronize_after_failed_publication(stream, "publish CUDA async allocation completion");
177 check_cuda_cleanup(cudaFreeAsync(data_, stream.native_handle()),
178 "cudaFreeAsync after cudaMallocAsync publication failure", resources_->device().ordinal());
179 synchronize_after_failed_publication(stream, "free CUDA async allocation after publication failure");
180 data_ = nullptr;
181 size_ = 0;
182 throw;
183 }
184 }
185
186 void reset() noexcept
187 {
188 if (resources_ == nullptr) return;
189 if (initial_completion_) resources_->defer_allocation_release_after(std::span(&initial_completion_, 1));
190
191 if (data_ != nullptr)
192 {
193 resources_->release_allocation(data_, force_blocking_deallocation_.load());
194 }
195
196 auto* resources = std::exchange(resources_, nullptr);
197 data_ = nullptr;
198 size_ = 0;
199 initial_completion_ = {};
200 if (registered_) resources->unregister_allocation();
201 registered_ = false;
202 }
203
204 DeviceResources* resources_ = nullptr;
205 T* data_ = nullptr;
206 std::size_t size_ = 0;
207 Completion initial_completion_;
208 std::atomic_bool force_blocking_deallocation_ = false;
209 bool registered_ = false;
210};
211
212} // namespace detail
213
220template <typename T>
221 requires std::is_trivially_copyable_v<T>
223 public:
224 static_assert(std::is_object_v<T>, "CUDA buffers require an object element type");
225 static_assert(!std::is_const_v<T>, "CUDA buffers own mutable storage; constness belongs on read guards");
226
227 using element_type = T;
232
234 explicit CudaBuffer(std::size_t size) : CudaBuffer(device_resources(), size) {}
235
238 : allocation_(std::make_shared<detail::CudaAllocation<T>>(resources, size)), size_(size),
239 writer_completion_(allocation_->initial_completion())
240 {}
241
242 CudaBuffer(CudaBuffer const&) = delete;
243 CudaBuffer& operator=(CudaBuffer const&) = delete;
244
245 CudaBuffer(CudaBuffer&& other) noexcept { this->move_from(other); }
246
247 CudaBuffer& operator=(CudaBuffer&& other) noexcept
248 {
249 if (this != &other)
250 {
251 this->reset();
252 this->move_from(other);
253 }
254 return *this;
255 }
256
257 ~CudaBuffer() { this->reset(); }
258
260 [[nodiscard]] std::size_t size() const noexcept { return size_; }
261
263 [[nodiscard]] std::size_t size_bytes() const noexcept { return size_ * sizeof(T); }
264
266 [[nodiscard]] bool empty() const noexcept { return size_ == 0; }
267
269 [[nodiscard]] DeviceResources& resources() const
270 {
271 CHECK(allocation_ != nullptr);
272 return allocation_->resources();
273 }
274
276 [[nodiscard]] Device device() const { return this->resources().device(); }
277
279 [[nodiscard]] std::size_t allocation_offset() const noexcept { return allocation_offset_; }
280
282 [[nodiscard]] bool shares_allocation_with(CudaBuffer const& other) const noexcept
283 {
284 return allocation_ != nullptr && allocation_ == other.allocation_;
285 }
286
291 [[nodiscard]] auto partition(std::span<CudaBufferRange const> ranges) && -> std::vector<CudaBuffer>
292 {
293 CHECK(allocation_ != nullptr, "cannot partition a moved-from CUDA buffer");
294 std::size_t previous_end = 0;
295 for (auto const& range : ranges)
296 {
297 CHECK(range.offset >= previous_end, range.offset, previous_end);
298 CHECK(range.offset <= size_ && range.size <= size_ - range.offset, range.offset, range.size, size_);
299 previous_end = range.offset + range.size;
300 }
301
302 std::lock_guard lock(state_mutex_);
303 CHECK(live_read_accesses_ == 0 && !live_write_access_,
304 "cannot partition a CUDA buffer while access guards are live", this->data(), live_read_accesses_,
305 live_write_access_);
306 std::vector<CudaBuffer> result;
307 result.reserve(ranges.size());
308 for (auto const& range : ranges)
309 {
310 result.push_back(CudaBuffer(allocation_, allocation_offset_ + range.offset, range.size, writer_completion_,
311 reader_completions_));
312 }
313 allocation_.reset();
314 allocation_offset_ = 0;
315 size_ = 0;
316 writer_completion_ = {};
317 reader_completions_.clear();
318 return result;
319 }
320
322 void synchronize() const
323 {
324 Completion writer_completion;
325 std::vector<Completion> reader_completions;
326 {
327 std::lock_guard lock(state_mutex_);
328 CHECK(live_read_accesses_ == 0 && !live_write_access_,
329 "cannot synchronize a CUDA buffer while access guards are live", this->data(), live_read_accesses_,
330 live_write_access_);
331 writer_completion = writer_completion_;
332 reader_completions = reader_completions_;
333 }
334 if (writer_completion)
335 {
336 writer_completion.synchronize();
337 }
338 for (Completion const& reader_completion : reader_completions)
339 {
340 reader_completion.synchronize();
341 }
342 }
343
345 [[nodiscard]] ReadAccess<T> read_synchronized_with(Stream const& stream) const
346 {
347 return ReadAccess<T>(*this, stream);
348 }
349
351 [[nodiscard]] WriteAccess<T> write_synchronized_with(Stream const& stream) { return WriteAccess<T>(*this, stream); }
352
354 [[nodiscard]] BlockingReadAccess<T> blocking_read_access() const { return BlockingReadAccess<T>(*this); }
355
358
361
363 [[nodiscard]] OwningReadAccess<T> into_read_synchronized_with(Stream const& stream) &&;
364
365 private:
366 CudaBuffer(std::shared_ptr<detail::CudaAllocation<T>> allocation, std::size_t allocation_offset, std::size_t size,
367 Completion writer_completion, std::vector<Completion> reader_completions)
368 : allocation_(std::move(allocation)), allocation_offset_(allocation_offset), size_(size),
369 writer_completion_(std::move(writer_completion)), reader_completions_(std::move(reader_completions))
370 {}
371
372 [[nodiscard]] T* data() noexcept
373 {
374 return allocation_ == nullptr || allocation_->data() == nullptr ? nullptr
375 : allocation_->data() + allocation_offset_;
376 }
377 [[nodiscard]] T const* data() const noexcept
378 {
379 return allocation_ == nullptr || allocation_->data() == nullptr ? nullptr
380 : allocation_->data() + allocation_offset_;
381 }
382
383 void acquire_read_access(Stream const& stream) const
384 {
385 CHECK(stream, "cannot synchronize CUDA buffer access with an empty stream", this->data());
386 Completion writer_completion;
387 {
388 std::lock_guard lock(state_mutex_);
389 CHECK(!live_write_access_, "cannot acquire CUDA read access while a write access is live", this->data());
390 writer_completion = writer_completion_;
391 ++live_read_accesses_;
392 }
393
394 try
395 {
396 if (writer_completion)
397 {
398 stream.wait_on(writer_completion);
399 }
400 }
401 catch (...)
402 {
403 std::lock_guard lock(state_mutex_);
404 CHECK(live_read_accesses_ > 0);
405 --live_read_accesses_;
406 throw;
407 }
408 }
409
410 void acquire_write_access(Stream const& stream) const
411 {
412 CHECK(stream, "cannot synchronize CUDA buffer access with an empty stream", this->data());
413 Completion writer_completion;
414 std::vector<Completion> reader_completions;
415 {
416 std::lock_guard lock(state_mutex_);
417 CHECK(!live_write_access_ && live_read_accesses_ == 0,
418 "cannot acquire CUDA write access while another access is live", this->data(), live_read_accesses_,
419 live_write_access_);
420 writer_completion = writer_completion_;
421 std::erase_if(reader_completions_, [](Completion const& completion) { return completion.ready(); });
422 reader_completions = reader_completions_;
423 live_write_access_ = true;
424 }
425
426 try
427 {
428 if (writer_completion)
429 {
430 stream.wait_on(writer_completion);
431 }
432 for (Completion const& reader_completion : reader_completions)
433 {
434 stream.wait_on(reader_completion);
435 }
436 }
437 catch (...)
438 {
439 std::lock_guard lock(state_mutex_);
440 CHECK(live_write_access_);
441 live_write_access_ = false;
442 throw;
443 }
444 }
445
446 void acquire_blocking_read_access() const
447 {
448 Completion writer_completion;
449 {
450 std::lock_guard lock(state_mutex_);
451 CHECK(!live_write_access_, "cannot acquire blocking CUDA read access while a write access is live",
452 this->data());
453 writer_completion = writer_completion_;
454 ++live_read_accesses_;
455 }
456
457 try
458 {
459 if (writer_completion)
460 {
461 writer_completion.synchronize();
462 }
463 }
464 catch (...)
465 {
466 this->release_blocking_read_access();
467 throw;
468 }
469 }
470
471 void acquire_blocking_write_access() const
472 {
473 Completion writer_completion;
474 std::vector<Completion> reader_completions;
475 {
476 std::lock_guard lock(state_mutex_);
477 CHECK(!live_write_access_ && live_read_accesses_ == 0,
478 "cannot acquire blocking CUDA write access while another access is live", this->data(),
479 live_read_accesses_, live_write_access_);
480 writer_completion = writer_completion_;
481 reader_completions = reader_completions_;
482 live_write_access_ = true;
483 }
484
485 try
486 {
487 if (writer_completion)
488 {
489 writer_completion.synchronize();
490 }
491 for (Completion const& reader_completion : reader_completions)
492 {
493 reader_completion.synchronize();
494 }
495 }
496 catch (...)
497 {
498 std::lock_guard lock(state_mutex_);
499 CHECK(live_write_access_);
500 live_write_access_ = false;
501 throw;
502 }
503 }
504
505 void release_blocking_read_access() const noexcept
506 {
507 std::lock_guard lock(state_mutex_);
508 CHECK(live_read_accesses_ > 0);
509 --live_read_accesses_;
510 }
511
512 void release_blocking_write_access(Completion completion = {}) const noexcept
513 {
514 std::lock_guard lock(state_mutex_);
515 CHECK(live_write_access_);
516 writer_completion_ = std::move(completion);
517 reader_completions_.clear();
518 live_write_access_ = false;
519 }
520
521 void release_read_access(Stream const& stream) const noexcept
522 {
523 try
524 {
525 Completion completion = stream.record_completion();
526 this->release_read_access(std::move(completion));
527 return;
528 }
529 catch (...)
530 {
531 detail::synchronize_after_failed_publication(stream, "publish CUDA buffer reader completion");
532 }
533 this->release_read_access(Completion{});
534 }
535
536 void release_write_access(Stream const& stream) const noexcept
537 {
538 try
539 {
540 Completion completion = stream.record_completion();
541 this->release_write_access(std::move(completion));
542 return;
543 }
544 catch (...)
545 {
546 detail::synchronize_after_failed_publication(stream, "publish CUDA buffer writer completion");
547 }
548
549 this->release_write_access(Completion{});
550 }
551
552 void release_read_access(Completion completion) const noexcept
553 {
554 std::lock_guard lock(state_mutex_);
555 CHECK(live_read_accesses_ > 0);
556 if (completion)
557 {
558 if (reader_completions_.size() == reader_completions_.capacity())
559 {
560 std::erase_if(reader_completions_,
561 [](Completion const& reader_completion) { return reader_completion.ready(); });
562 }
563 reader_completions_.push_back(std::move(completion));
564 }
565 --live_read_accesses_;
566 }
567
568 void release_write_access(Completion completion) const noexcept
569 {
570 std::lock_guard lock(state_mutex_);
571 CHECK(live_write_access_);
572 writer_completion_ = std::move(completion);
573 reader_completions_.clear();
574 live_write_access_ = false;
575 }
576
577 void prepare_owning_read_access(Stream const& stream) const
578 {
579 CHECK(stream, "cannot synchronize owning CUDA buffer access with an empty stream", this->data());
580 Completion writer_completion;
581 {
582 std::lock_guard lock(state_mutex_);
583 CHECK(live_read_accesses_ == 0 && !live_write_access_,
584 "cannot take owning CUDA buffer access while another access is live", this->data(), live_read_accesses_,
585 live_write_access_);
586 writer_completion = writer_completion_;
587 }
588 if (writer_completion)
589 {
590 stream.wait_on(writer_completion);
591 }
592 }
593
594 void prepare_owning_blocking_read_access() const
595 {
596 Completion writer_completion;
597 {
598 std::lock_guard lock(state_mutex_);
599 CHECK(live_read_accesses_ == 0 && !live_write_access_,
600 "cannot take owning CUDA buffer access while another access is live", this->data(), live_read_accesses_,
601 live_write_access_);
602 writer_completion = writer_completion_;
603 }
604 if (writer_completion)
605 {
606 writer_completion.synchronize();
607 }
608 }
609
610 void publish_owning_read_completion(Stream const& stream) const noexcept
611 {
612 try
613 {
614 Completion completion = stream.record_completion();
615 std::lock_guard lock(state_mutex_);
616 if (reader_completions_.size() == reader_completions_.capacity())
617 {
618 std::erase_if(reader_completions_,
619 [](Completion const& reader_completion) { return reader_completion.ready(); });
620 }
621 reader_completions_.push_back(std::move(completion));
622 return;
623 }
624 catch (...)
625 {
626 detail::synchronize_after_failed_publication(stream, "publish owning CUDA buffer reader completion");
627 }
628 }
629
630 void reset() noexcept { this->reset_impl(); }
631
632 void reset_owned() noexcept
633 {
634 if (allocation_ != nullptr) allocation_->require_blocking_deallocation();
635 this->reset_impl();
636 }
637
638 void reset_impl() noexcept
639 {
640 if (allocation_ == nullptr)
641 {
642 return;
643 }
644
645 Completion writer_completion;
646 std::vector<Completion> reader_completions;
647 {
648 std::lock_guard lock(state_mutex_);
649 CHECK(live_read_accesses_ == 0 && !live_write_access_,
650 "cannot destroy or reset a CUDA buffer while access guards are live", this->data(), live_read_accesses_,
651 live_write_access_);
652 writer_completion = std::move(writer_completion_);
653 reader_completions = std::move(reader_completions_);
654 }
655 allocation_->defer_release_after(writer_completion, reader_completions);
656
657 allocation_.reset();
658 allocation_offset_ = 0;
659 size_ = 0;
660 writer_completion_ = {};
661 reader_completions_.clear();
662 }
663
664 void move_from(CudaBuffer& other) noexcept
665 {
666 if (other.allocation_ == nullptr)
667 {
668 return;
669 }
670
671 std::lock_guard lock(other.state_mutex_);
672 CHECK(other.live_read_accesses_ == 0 && !other.live_write_access_,
673 "cannot move a CUDA buffer while access guards are live", other.data(), other.live_read_accesses_,
674 other.live_write_access_);
675 allocation_ = std::move(other.allocation_);
676 allocation_offset_ = std::exchange(other.allocation_offset_, 0);
677 size_ = other.size_;
678 writer_completion_ = std::move(other.writer_completion_);
679 reader_completions_ = std::move(other.reader_completions_);
680 live_read_accesses_ = 0;
681 live_write_access_ = false;
682 other.size_ = 0;
683 }
684
685 std::shared_ptr<detail::CudaAllocation<T>> allocation_;
686 std::size_t allocation_offset_ = 0;
687 std::size_t size_ = 0;
688 mutable std::mutex state_mutex_;
689 mutable Completion writer_completion_;
690 mutable std::vector<Completion> reader_completions_;
691 mutable std::size_t live_read_accesses_ = 0;
692 mutable bool live_write_access_ = false;
693
694 template <typename> friend class ReadAccess;
695 template <typename> friend class WriteAccess;
696 template <typename> friend class BlockingReadAccess;
697 template <typename> friend class BlockingWriteAccess;
698 template <typename> friend class OwningReadAccess;
699 template <typename> friend class PartitionedReadAccess;
700 template <typename> friend class PartitionedWriteAccess;
701};
702
707template <typename T>
708 requires std::is_trivially_copyable_v<T>
710 public:
711 using element_type = T;
713
714 PartitionedCudaBuffer(DeviceResources& resources, std::size_t size, std::span<CudaBufferRange const> ranges)
715 : resources_(&resources), size_(size), buffers_(make_buffers(resources, size, ranges))
716 {}
717
721 : resources_(std::exchange(other.resources_, nullptr)), size_(std::exchange(other.size_, 0)),
722 buffers_(std::move(other.buffers_))
723 {}
725 {
726 if (this != &other)
727 {
728 buffers_ = std::move(other.buffers_);
729 resources_ = std::exchange(other.resources_, nullptr);
730 size_ = std::exchange(other.size_, 0);
731 }
732 return *this;
733 }
734
736 [[nodiscard]] std::size_t size() const noexcept { return size_; }
737
739 [[nodiscard]] std::size_t buffer_count() const noexcept { return buffers_.size(); }
740
742 [[nodiscard]] CudaBuffer<T>& buffer(std::size_t ordinal) { return buffers_[ordinal]; }
743
745 [[nodiscard]] CudaBuffer<T> const& buffer(std::size_t ordinal) const { return buffers_[ordinal]; }
746
748 [[nodiscard]] DeviceResources& resources() const
749 {
750 CHECK(resources_ != nullptr);
751 return *resources_;
752 }
753
755 [[nodiscard]] Device device() const { return this->resources().device(); }
756
760 [[nodiscard]] auto read_synchronized_with(Stream const& stream) const -> PartitionedReadAccess<T>;
761
765 [[nodiscard]] auto write_synchronized_with(Stream const& stream) -> PartitionedWriteAccess<T>;
766
767 private:
768 void require_complete_partition() const
769 {
770 std::size_t next_offset = 0;
771 for (auto const& buffer : buffers_)
772 {
773 CHECK_EQUAL(buffer.allocation_offset(), next_offset);
774 next_offset += buffer.size();
775 }
776 CHECK_EQUAL(next_offset, size_);
777 }
778
779 static auto make_buffers(DeviceResources& resources, std::size_t size,
780 std::span<CudaBufferRange const> ranges) -> std::vector<CudaBuffer<T>>
781 {
782 CudaBuffer<T> allocation(resources, size);
783 return std::move(allocation).partition(ranges);
784 }
785
786 DeviceResources* resources_ = nullptr;
787 std::size_t size_ = 0;
788 std::vector<CudaBuffer<T>> buffers_;
789
790 template <typename> friend class PartitionedReadAccess;
791 template <typename> friend class PartitionedWriteAccess;
792};
793
798template <typename T> class ReadAccess {
799 public:
800 using element_type = T;
801
802 ReadAccess(ReadAccess const&) = delete;
803 ReadAccess& operator=(ReadAccess const&) = delete;
804 ReadAccess(ReadAccess&& other) noexcept
805 : storage_(std::exchange(other.storage_, nullptr)), stream_(std::move(other.stream_))
806 {}
807 ReadAccess& operator=(ReadAccess&& other) noexcept
808 {
809 if (this != &other)
810 {
811 this->release();
812 storage_ = std::exchange(other.storage_, nullptr);
813 stream_ = std::move(other.stream_);
814 }
815 return *this;
816 }
817 ~ReadAccess() { this->release(); }
818
820 [[nodiscard]] T const* data() const noexcept { return storage_ == nullptr ? nullptr : storage_->data(); }
821
823 [[nodiscard]] CudaBuffer<T> const& storage() const
824 {
825 CHECK(storage_ != nullptr, "cannot inspect storage through a released CUDA read access");
826 return *storage_;
827 }
828
830 [[nodiscard]] std::size_t size() const noexcept { return storage_ == nullptr ? 0 : storage_->size(); }
831
833 [[nodiscard]] std::size_t size_bytes() const noexcept { return storage_ == nullptr ? 0 : storage_->size_bytes(); }
834
836 void release() noexcept
837 {
838 if (storage_ == nullptr)
839 {
840 return;
841 }
842 storage_->release_read_access(stream_);
843 storage_ = nullptr;
844 stream_ = {};
845 }
846
848 void release_with_completion(Completion completion) noexcept
849 {
850 if (storage_ == nullptr) return;
851 CHECK(completion);
852 storage_->release_read_access(std::move(completion));
853 storage_ = nullptr;
854 stream_ = {};
855 }
856
859 {
860 if (storage_ == nullptr) return;
861 storage_->release_read_access(Completion{});
862 storage_ = nullptr;
863 stream_ = {};
864 }
865
866 private:
867 ReadAccess(CudaBuffer<T> const& storage, Stream const& stream) : stream_(stream)
868 {
869 storage.acquire_read_access(stream_);
870 storage_ = &storage;
871 }
872
873 CudaBuffer<T> const* storage_ = nullptr;
874 Stream stream_;
875
876 friend class CudaBuffer<T>;
877};
878
883template <typename T> class WriteAccess {
884 public:
885 using element_type = T;
886
887 WriteAccess(WriteAccess const&) = delete;
889 WriteAccess(WriteAccess&& other) noexcept
890 : storage_(std::exchange(other.storage_, nullptr)), stream_(std::move(other.stream_))
891 {}
893 {
894 if (this != &other)
895 {
896 this->release();
897 storage_ = std::exchange(other.storage_, nullptr);
898 stream_ = std::move(other.stream_);
899 }
900 return *this;
901 }
902 ~WriteAccess() { this->release(); }
903
905 [[nodiscard]] T* data() const noexcept { return storage_ == nullptr ? nullptr : storage_->data(); }
906
908 [[nodiscard]] CudaBuffer<T>& storage()
909 {
910 CHECK(storage_ != nullptr, "cannot inspect storage through a released CUDA write access");
911 return *storage_;
912 }
913
915 [[nodiscard]] CudaBuffer<T> const& storage() const
916 {
917 CHECK(storage_ != nullptr, "cannot inspect storage through a released CUDA write access");
918 return *storage_;
919 }
920
922 [[nodiscard]] std::size_t size() const noexcept { return storage_ == nullptr ? 0 : storage_->size(); }
923
925 [[nodiscard]] std::size_t size_bytes() const noexcept { return storage_ == nullptr ? 0 : storage_->size_bytes(); }
926
928 void release() noexcept
929 {
930 if (storage_ == nullptr)
931 {
932 return;
933 }
934 storage_->release_write_access(stream_);
935 storage_ = nullptr;
936 stream_ = {};
937 }
938
940 void release_with_completion(Completion completion) noexcept
941 {
942 if (storage_ == nullptr) return;
943 CHECK(completion);
944 storage_->release_write_access(std::move(completion));
945 storage_ = nullptr;
946 stream_ = {};
947 }
948
951 {
952 if (storage_ == nullptr) return;
953 storage_->release_write_access(Completion{});
954 storage_ = nullptr;
955 stream_ = {};
956 }
957
958 private:
959 WriteAccess(CudaBuffer<T>& storage, Stream const& stream) : stream_(stream)
960 {
961 storage.acquire_write_access(stream_);
962 storage_ = &storage;
963 }
964
965 CudaBuffer<T>* storage_ = nullptr;
966 Stream stream_;
967
968 friend class CudaBuffer<T>;
969};
970
975template <typename T> class PartitionedReadAccess {
976 public:
977 using element_type = T;
978
982 : storage_(std::exchange(other.storage_, nullptr)), accesses_(std::move(other.accesses_)),
983 stream_(std::move(other.stream_))
984 {}
986 {
987 if (this != &other)
988 {
989 this->release();
990 storage_ = std::exchange(other.storage_, nullptr);
991 accesses_ = std::move(other.accesses_);
992 stream_ = std::move(other.stream_);
993 }
994 return *this;
995 }
997
999 [[nodiscard]] T const* data() const noexcept { return accesses_.empty() ? nullptr : accesses_.front().data(); }
1000
1002 [[nodiscard]] std::size_t size() const noexcept { return storage_ == nullptr ? 0 : storage_->size(); }
1003
1005 void release() noexcept
1006 {
1007 if (storage_ == nullptr) return;
1008 try
1009 {
1011 return;
1012 }
1013 catch (...)
1014 {
1015 detail::synchronize_after_failed_publication(stream_, "publish partitioned CUDA reader completion");
1016 }
1018 }
1019
1021 void release_with_completion(Completion completion) noexcept
1022 {
1023 if (storage_ == nullptr) return;
1024 CHECK(completion);
1025 for (auto& access : accesses_)
1026 access.release_with_completion(completion);
1027 this->clear();
1028 }
1029
1032 {
1033 if (storage_ == nullptr) return;
1034 for (auto& access : accesses_)
1035 access.release_after_synchronization();
1036 this->clear();
1037 }
1038
1039 private:
1040 PartitionedReadAccess(PartitionedCudaBuffer<T> const& storage, Stream const& stream)
1041 : storage_(&storage), stream_(stream)
1042 {
1043 storage.require_complete_partition();
1044 accesses_.reserve(storage.buffers_.size());
1045 for (auto const& buffer : storage.buffers_)
1046 accesses_.push_back(buffer.read_synchronized_with(stream_));
1047 }
1048
1049 void clear() noexcept
1050 {
1051 accesses_.clear();
1052 storage_ = nullptr;
1053 stream_ = {};
1054 }
1055
1056 PartitionedCudaBuffer<T> const* storage_ = nullptr;
1057 std::vector<ReadAccess<T>> accesses_;
1058 Stream stream_;
1059
1060 friend class PartitionedCudaBuffer<T>;
1061};
1062
1067template <typename T> class PartitionedWriteAccess {
1068 public:
1069 using element_type = T;
1070
1074 : storage_(std::exchange(other.storage_, nullptr)), accesses_(std::move(other.accesses_)),
1075 stream_(std::move(other.stream_))
1076 {}
1078 {
1079 if (this != &other)
1080 {
1081 this->release();
1082 storage_ = std::exchange(other.storage_, nullptr);
1083 accesses_ = std::move(other.accesses_);
1084 stream_ = std::move(other.stream_);
1085 }
1086 return *this;
1087 }
1089
1091 [[nodiscard]] T* data() const noexcept { return accesses_.empty() ? nullptr : accesses_.front().data(); }
1092
1094 [[nodiscard]] std::size_t size() const noexcept { return storage_ == nullptr ? 0 : storage_->size(); }
1095
1097 void release() noexcept
1098 {
1099 if (storage_ == nullptr) return;
1100 try
1101 {
1103 return;
1104 }
1105 catch (...)
1106 {
1107 detail::synchronize_after_failed_publication(stream_, "publish partitioned CUDA writer completion");
1108 }
1110 }
1111
1113 void release_with_completion(Completion completion) noexcept
1114 {
1115 if (storage_ == nullptr) return;
1116 CHECK(completion);
1117 for (auto& access : accesses_)
1118 access.release_with_completion(completion);
1119 this->clear();
1120 }
1121
1124 {
1125 if (storage_ == nullptr) return;
1126 for (auto& access : accesses_)
1127 access.release_after_synchronization();
1128 this->clear();
1129 }
1130
1131 private:
1133 : storage_(&storage), stream_(stream)
1134 {
1135 storage.require_complete_partition();
1136 accesses_.reserve(storage.buffers_.size());
1137 for (auto& buffer : storage.buffers_)
1138 accesses_.push_back(buffer.write_synchronized_with(stream_));
1139 }
1140
1141 void clear() noexcept
1142 {
1143 accesses_.clear();
1144 storage_ = nullptr;
1145 stream_ = {};
1146 }
1147
1148 PartitionedCudaBuffer<T>* storage_ = nullptr;
1149 std::vector<WriteAccess<T>> accesses_;
1150 Stream stream_;
1151
1152 friend class PartitionedCudaBuffer<T>;
1153};
1154
1161 public:
1163 explicit AccessCompletion(Stream const& stream) noexcept
1164 {
1165 try
1166 {
1167 completion_ = stream.record_completion();
1168 }
1169 catch (...)
1170 {
1171 detail::synchronize_after_failed_publication(stream, "publish shared CUDA buffer access completion");
1172 }
1173 }
1174
1177 AccessCompletion(AccessCompletion&&) noexcept = default;
1178 AccessCompletion& operator=(AccessCompletion&&) noexcept = default;
1179
1181 template <class Access> void release(Access& access) const noexcept
1182 {
1183 if (completion_)
1184 access.release_with_completion(completion_);
1185 else
1186 access.release_after_synchronization();
1187 }
1188
1189 private:
1190 Completion completion_;
1191};
1192
1193template <typename T>
1194 requires std::is_trivially_copyable_v<T>
1196{
1197 return PartitionedReadAccess<T>{*this, stream};
1198}
1199
1200template <typename T>
1201 requires std::is_trivially_copyable_v<T>
1206
1211template <typename T> class BlockingReadAccess {
1212 public:
1213 using element_type = T;
1214
1217 BlockingReadAccess(BlockingReadAccess&& other) noexcept : storage_(std::exchange(other.storage_, nullptr)) {}
1219 {
1220 if (this != &other)
1221 {
1222 this->release();
1223 storage_ = std::exchange(other.storage_, nullptr);
1224 }
1225 return *this;
1226 }
1228
1230 [[nodiscard]] T const* data() const noexcept { return storage_ == nullptr ? nullptr : storage_->data(); }
1231
1233 [[nodiscard]] CudaBuffer<T> const& storage() const
1234 {
1235 CHECK(storage_ != nullptr, "cannot inspect storage through a released blocking CUDA read access");
1236 return *storage_;
1237 }
1238
1240 [[nodiscard]] std::size_t size() const noexcept { return storage_ == nullptr ? 0 : storage_->size(); }
1241
1243 void release() noexcept
1244 {
1245 if (storage_ == nullptr) return;
1246 storage_->release_blocking_read_access();
1247 storage_ = nullptr;
1248 }
1249
1250 private:
1252 {
1253 storage.acquire_blocking_read_access();
1254 storage_ = &storage;
1255 }
1256
1257 CudaBuffer<T> const* storage_ = nullptr;
1258
1259 friend class CudaBuffer<T>;
1260};
1261
1266template <typename T> class BlockingWriteAccess {
1267 public:
1268 using element_type = T;
1269
1272 BlockingWriteAccess(BlockingWriteAccess&& other) noexcept : storage_(std::exchange(other.storage_, nullptr)) {}
1274 {
1275 if (this != &other)
1276 {
1277 this->release();
1278 storage_ = std::exchange(other.storage_, nullptr);
1279 }
1280 return *this;
1281 }
1283
1285 [[nodiscard]] T* data() const noexcept { return storage_ == nullptr ? nullptr : storage_->data(); }
1286
1288 [[nodiscard]] CudaBuffer<T>& storage()
1289 {
1290 CHECK(storage_ != nullptr, "cannot inspect storage through a released blocking CUDA write access");
1291 return *storage_;
1292 }
1293
1295 [[nodiscard]] CudaBuffer<T> const& storage() const
1296 {
1297 CHECK(storage_ != nullptr, "cannot inspect storage through a released blocking CUDA write access");
1298 return *storage_;
1299 }
1300
1302 [[nodiscard]] std::size_t size() const noexcept { return storage_ == nullptr ? 0 : storage_->size(); }
1303
1305 void release() noexcept
1306 {
1307 if (storage_ == nullptr) return;
1308 storage_->release_blocking_write_access();
1309 storage_ = nullptr;
1310 }
1311
1315 void release_with_completion(Completion completion) noexcept
1316 {
1317 if (storage_ == nullptr) return;
1318 CHECK(completion);
1319 storage_->release_blocking_write_access(std::move(completion));
1320 storage_ = nullptr;
1321 }
1322
1323 private:
1325 {
1326 storage.acquire_blocking_write_access();
1327 storage_ = &storage;
1328 }
1329
1330 CudaBuffer<T>* storage_ = nullptr;
1331
1332 friend class CudaBuffer<T>;
1333};
1334
1341template <typename T> class OwningReadAccess {
1342 public:
1343 using element_type = T;
1344
1347
1349 : storage_(std::move(other.storage_)), stream_(std::move(other.stream_)),
1350 stream_ordered_(std::exchange(other.stream_ordered_, false)), active_(std::exchange(other.active_, false))
1351 {}
1352
1354
1356
1358 [[nodiscard]] T const* data() const noexcept { return active_ ? storage_.data() : nullptr; }
1359
1361 void release() noexcept
1362 {
1363 if (!active_) return;
1364 bool const blocking_deallocation = !stream_ordered_;
1365 if (stream_ordered_)
1366 {
1367 storage_.publish_owning_read_completion(stream_);
1368 }
1369 stream_ = {};
1370 stream_ordered_ = false;
1371 active_ = false;
1372 if (blocking_deallocation)
1373 storage_.reset_owned();
1374 else
1375 storage_.reset();
1376 }
1377
1378 private:
1379 explicit OwningReadAccess(CudaBuffer<T>&& storage) : storage_(std::move(storage)), active_(true)
1380 {
1381 storage_.allocation_->require_blocking_deallocation();
1382 storage_.prepare_owning_blocking_read_access();
1383 }
1384
1385 OwningReadAccess(CudaBuffer<T>&& storage, Stream const& stream)
1386 : storage_(std::move(storage)), stream_(stream), stream_ordered_(true), active_(true)
1387 {
1388 storage_.prepare_owning_read_access(stream_);
1389 }
1390
1391 CudaBuffer<T> storage_;
1392 Stream stream_;
1393 bool stream_ordered_ = false;
1394 bool active_ = false;
1395
1396 friend class CudaBuffer<T>;
1397};
1398
1399template <typename T>
1400 requires std::is_trivially_copyable_v<T>
1405
1406template <typename T>
1407 requires std::is_trivially_copyable_v<T>
1409{
1410 return OwningReadAccess<T>{std::move(*this), stream};
1411}
1412
1413} // namespace uni20::cuda
One operation-tail completion shared by multiple CUDA buffer accesses.
Definition buffer.hpp:1160
void release(Access &access) const noexcept
Release one participating access through the shared completion.
Definition buffer.hpp:1181
AccessCompletion(AccessCompletion const &)=delete
AccessCompletion(AccessCompletion &&) noexcept=default
AccessCompletion & operator=(AccessCompletion const &)=delete
AccessCompletion(Stream const &stream) noexcept
Record the current stream tail, synchronizing only if publication fails.
Definition buffer.hpp:1163
Scoped read-only CUDA pointer access after a host-side synchronization.
Definition buffer.hpp:1211
T element_type
Definition buffer.hpp:1213
~BlockingReadAccess()
Definition buffer.hpp:1227
BlockingReadAccess(BlockingReadAccess const &)=delete
CudaBuffer< T > const & storage() const
Return the CUDA buffer retained by this access object.
Definition buffer.hpp:1233
T const * data() const noexcept
Return the typed device pointer for a blocking runtime operation.
Definition buffer.hpp:1230
void release() noexcept
End the scoped host-synchronized read access.
Definition buffer.hpp:1243
BlockingReadAccess(BlockingReadAccess &&other) noexcept
Definition buffer.hpp:1217
std::size_t size() const noexcept
Return the number of typed elements in the allocation.
Definition buffer.hpp:1240
BlockingReadAccess & operator=(BlockingReadAccess const &)=delete
BlockingReadAccess & operator=(BlockingReadAccess &&other) noexcept
Definition buffer.hpp:1218
Scoped read/write CUDA pointer access after a host-side synchronization.
Definition buffer.hpp:1266
std::size_t size() const noexcept
Return the number of typed elements in the allocation.
Definition buffer.hpp:1302
BlockingWriteAccess(BlockingWriteAccess &&other) noexcept
Definition buffer.hpp:1272
BlockingWriteAccess(BlockingWriteAccess const &)=delete
T * data() const noexcept
Return the typed device pointer for a blocking runtime operation.
Definition buffer.hpp:1285
CudaBuffer< T > const & storage() const
Return the read-only CUDA buffer retained by this access object.
Definition buffer.hpp:1295
CudaBuffer< T > & storage()
Return the mutable CUDA buffer retained by this access object.
Definition buffer.hpp:1288
BlockingWriteAccess & operator=(BlockingWriteAccess const &)=delete
~BlockingWriteAccess()
Definition buffer.hpp:1282
void release_with_completion(Completion completion) noexcept
Publish externally submitted device completion and end the scoped access.
Definition buffer.hpp:1315
BlockingWriteAccess & operator=(BlockingWriteAccess &&other) noexcept
Definition buffer.hpp:1273
T element_type
Definition buffer.hpp:1268
void release() noexcept
End the scoped host-synchronized write access.
Definition buffer.hpp:1305
Shared completion token for one submitted CUDA operation.
Definition runtime.hpp:104
void synchronize() const
Wait on the host for the operation to complete.
Definition runtime.cpp:327
Move-only logical CUDA buffer and completion state.
Definition buffer.hpp:222
WriteAccess< T > write_synchronized_with(Stream const &stream)
Acquire scoped read/write access synchronized with stream.
Definition buffer.hpp:351
CudaBuffer(CudaBuffer const &)=delete
void synchronize() const
Wait for every submitted operation currently involving this buffer.
Definition buffer.hpp:322
bool empty() const noexcept
Return whether this is a zero-sized or moved-from allocation.
Definition buffer.hpp:266
CudaBuffer(CudaBuffer &&other) noexcept
Definition buffer.hpp:245
BlockingReadAccess< T > blocking_read_access() const
Acquire read-only access after host-waiting for prior CUDA work.
Definition buffer.hpp:354
~CudaBuffer()
Definition buffer.hpp:257
std::size_t allocation_offset() const noexcept
Return this logical buffer's offset in its physical allocation.
Definition buffer.hpp:279
std::size_t size_bytes() const noexcept
Return the allocation size in bytes.
Definition buffer.hpp:263
CudaBuffer & operator=(CudaBuffer &&other) noexcept
Definition buffer.hpp:247
CudaBuffer & operator=(CudaBuffer const &)=delete
CudaBuffer(std::size_t size)
Allocate size elements on the installed runtime's default CUDA device.
Definition buffer.hpp:234
ReadAccess< T > read_synchronized_with(Stream const &stream) const
Acquire scoped read-only access synchronized with stream.
Definition buffer.hpp:345
OwningReadAccess< T > into_blocking_read_access() &&
Move this buffer into an owning host-synchronized read access.
Definition buffer.hpp:1401
bool shares_allocation_with(CudaBuffer const &other) const noexcept
Return whether another logical buffer shares this physical allocation.
Definition buffer.hpp:282
OwningReadAccess< T > into_read_synchronized_with(Stream const &stream) &&
Move this buffer into an owning stream-ordered read access.
Definition buffer.hpp:1408
Device device() const
Return the allocation's CUDA device.
Definition buffer.hpp:276
CudaBuffer(DeviceResources &resources, std::size_t size)
Allocate size elements from one CUDA device's resources.
Definition buffer.hpp:237
auto partition(std::span< CudaBufferRange const > ranges) &&-> std::vector< CudaBuffer >
Consume this buffer into statically disjoint logical child buffers.
Definition buffer.hpp:291
std::size_t size() const noexcept
Return the number of typed elements in the allocation.
Definition buffer.hpp:260
DeviceResources & resources() const
Return the device resources borrowed by this buffer.
Definition buffer.hpp:269
T element_type
Definition buffer.hpp:227
BlockingWriteAccess< T > blocking_write_access()
Acquire read/write access after host-waiting for prior CUDA work.
Definition buffer.hpp:357
Mutable stream and provider resources associated with one CUDA device.
Definition runtime.hpp:222
Device device() const noexcept
Return the device served by this resource set.
Definition runtime.hpp:241
Validated CUDA device identity with cached immutable capabilities.
Definition device.hpp:105
Read access that owns a moved CUDA buffer.
Definition buffer.hpp:1341
OwningReadAccess(OwningReadAccess &&other) noexcept
Definition buffer.hpp:1348
T element_type
Definition buffer.hpp:1343
T const * data() const noexcept
Return the typed device pointer owned by this access state.
Definition buffer.hpp:1358
~OwningReadAccess()
Definition buffer.hpp:1355
friend class CudaBuffer< T >
Definition buffer.hpp:1396
OwningReadAccess & operator=(OwningReadAccess const &)=delete
void release() noexcept
End access and destroy the owned allocation.
Definition buffer.hpp:1361
OwningReadAccess(OwningReadAccess const &)=delete
OwningReadAccess & operator=(OwningReadAccess &&)=delete
One CUDA allocation partitioned into independent logical buffers.
Definition buffer.hpp:709
PartitionedCudaBuffer & operator=(PartitionedCudaBuffer &&other) noexcept
Definition buffer.hpp:724
std::size_t buffer_count() const noexcept
Return the number of logical child buffers.
Definition buffer.hpp:739
auto read_synchronized_with(Stream const &stream) const -> PartitionedReadAccess< T >
Acquire read access to the complete contiguous allocation.
Definition buffer.hpp:1195
PartitionedCudaBuffer(DeviceResources &resources, std::size_t size, std::span< CudaBufferRange const > ranges)
Definition buffer.hpp:714
PartitionedCudaBuffer(PartitionedCudaBuffer &&other) noexcept
Definition buffer.hpp:720
Device device() const
Return the CUDA device containing the physical allocation.
Definition buffer.hpp:755
std::size_t size() const noexcept
Return the total physical allocation size in elements.
Definition buffer.hpp:736
PartitionedCudaBuffer & operator=(PartitionedCudaBuffer const &)=delete
DeviceResources & resources() const
Return the device resources shared by every child buffer.
Definition buffer.hpp:748
T element_type
Definition buffer.hpp:711
PartitionedCudaBuffer(PartitionedCudaBuffer const &)=delete
CudaBuffer< T > & buffer(std::size_t ordinal)
Return one mutable logical child buffer.
Definition buffer.hpp:742
auto write_synchronized_with(Stream const &stream) -> PartitionedWriteAccess< T >
Acquire write access to the complete contiguous allocation.
Definition buffer.hpp:1202
CudaBuffer< T > const & buffer(std::size_t ordinal) const
Return one read-only logical child buffer.
Definition buffer.hpp:745
Scoped read access to every child of one partitioned CUDA allocation.
Definition buffer.hpp:975
void release() noexcept
Record and publish one shared reader completion.
Definition buffer.hpp:1005
PartitionedReadAccess & operator=(PartitionedReadAccess const &)=delete
PartitionedReadAccess(PartitionedReadAccess const &)=delete
std::size_t size() const noexcept
Return the physical allocation size in elements.
Definition buffer.hpp:1002
T const * data() const noexcept
Return the first element of the complete physical allocation.
Definition buffer.hpp:999
T element_type
Definition buffer.hpp:977
void release_after_synchronization() noexcept
End access after the caller has synchronized the operation stream.
Definition buffer.hpp:1031
PartitionedReadAccess & operator=(PartitionedReadAccess &&other) noexcept
Definition buffer.hpp:985
PartitionedReadAccess(PartitionedReadAccess &&other) noexcept
Definition buffer.hpp:981
~PartitionedReadAccess()
Definition buffer.hpp:996
void release_with_completion(Completion completion) noexcept
Publish a caller-recorded completion to every child ledger.
Definition buffer.hpp:1021
friend class PartitionedCudaBuffer< T >
Definition buffer.hpp:1060
Scoped write access to every child of one partitioned CUDA allocation.
Definition buffer.hpp:1067
~PartitionedWriteAccess()
Definition buffer.hpp:1088
PartitionedWriteAccess & operator=(PartitionedWriteAccess const &)=delete
PartitionedWriteAccess(PartitionedWriteAccess const &)=delete
void release_after_synchronization() noexcept
End access after the caller has synchronized the operation stream.
Definition buffer.hpp:1123
PartitionedWriteAccess & operator=(PartitionedWriteAccess &&other) noexcept
Definition buffer.hpp:1077
T element_type
Definition buffer.hpp:1069
PartitionedWriteAccess(PartitionedWriteAccess &&other) noexcept
Definition buffer.hpp:1073
std::size_t size() const noexcept
Return the physical allocation size in elements.
Definition buffer.hpp:1094
void release_with_completion(Completion completion) noexcept
Publish a caller-recorded completion to every child ledger.
Definition buffer.hpp:1113
void release() noexcept
Record and publish one shared writer completion.
Definition buffer.hpp:1097
T * data() const noexcept
Return the first element of the complete physical allocation.
Definition buffer.hpp:1091
friend class PartitionedCudaBuffer< T >
Definition buffer.hpp:1152
Scoped read-only access to a CUDA buffer on one stream.
Definition buffer.hpp:798
T const * data() const noexcept
Return the typed device pointer for read-only CUDA work.
Definition buffer.hpp:820
T element_type
Definition buffer.hpp:800
std::size_t size() const noexcept
Return the number of typed elements in the view.
Definition buffer.hpp:830
friend class CudaBuffer< T >
Definition buffer.hpp:876
void release() noexcept
Publish this read completion and end the scoped access.
Definition buffer.hpp:836
void release_with_completion(Completion completion) noexcept
Publish a caller-recorded completion and end scoped access.
Definition buffer.hpp:848
ReadAccess(ReadAccess &&other) noexcept
Definition buffer.hpp:804
std::size_t size_bytes() const noexcept
Return the number of bytes in the view.
Definition buffer.hpp:833
ReadAccess & operator=(ReadAccess const &)=delete
void release_after_synchronization() noexcept
End access after the caller has synchronized the operation stream.
Definition buffer.hpp:858
ReadAccess & operator=(ReadAccess &&other) noexcept
Definition buffer.hpp:807
~ReadAccess()
Definition buffer.hpp:817
CudaBuffer< T > const & storage() const
Return the CUDA buffer retained by this access object.
Definition buffer.hpp:823
ReadAccess(ReadAccess const &)=delete
Reference-counted lease of one CUDA stream-pool slot.
Definition runtime.hpp:65
Completion record_completion() const
Record and return an immutable completion for the current stream tail.
Definition runtime.cpp:298
Scoped read/write access to a CUDA buffer on one stream.
Definition buffer.hpp:883
T element_type
Definition buffer.hpp:885
CudaBuffer< T > & storage()
Return the mutable CUDA buffer retained by this access object.
Definition buffer.hpp:908
CudaBuffer< T > const & storage() const
Return the read-only CUDA buffer retained by this access object.
Definition buffer.hpp:915
WriteAccess & operator=(WriteAccess &&other) noexcept
Definition buffer.hpp:892
std::size_t size() const noexcept
Return the number of typed elements in the view.
Definition buffer.hpp:922
friend class CudaBuffer< T >
Definition buffer.hpp:968
void release_with_completion(Completion completion) noexcept
Publish a caller-recorded completion and end scoped access.
Definition buffer.hpp:940
WriteAccess(WriteAccess const &)=delete
void release_after_synchronization() noexcept
End access after the caller has synchronized the operation stream.
Definition buffer.hpp:950
void release() noexcept
Publish this write completion and end the scoped access.
Definition buffer.hpp:928
WriteAccess(WriteAccess &&other) noexcept
Definition buffer.hpp:889
std::size_t size_bytes() const noexcept
Return the number of bytes in the view.
Definition buffer.hpp:925
~WriteAccess()
Definition buffer.hpp:902
WriteAccess & operator=(WriteAccess const &)=delete
T * data() const noexcept
Return the typed device pointer for mutating CUDA work.
Definition buffer.hpp:905
Structured CUDA runtime failures and checked-call helpers.
CUDA device discovery and immutable hardware capability snapshots.
Definition qnum.hpp:403
std::pair< int, int > size(std::FILE *stream)
Return the terminal size associated with a C stream.
Definition terminal.cpp:504
void check(cublasStatus_t status, std::string_view operation, int device, std::source_location where)
Raise unless a cuBLAS call returned CUBLAS_STATUS_SUCCESS.
Definition cublas_error.cpp:52
Definition cublas/execution.hpp:18
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
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
std::size_t bytes
Definition presentation.cpp:18
CUDA device, stream, resource, and scoped runtime primitives.
One logical range used to partition a CUDA allocation.
Definition buffer.hpp:42
std::size_t offset
Definition buffer.hpp:43
std::size_t size
Definition buffer.hpp:44
#define CHECK(cond,...)
Definition trace.hpp:299
#define PANIC(...)
Definition trace.hpp:382
#define CHECK_EQUAL(a, b,...)
Definition trace.hpp:309