rmm_resource_adaptor_impl.hpp
1 
6 #pragma once
7 
8 #include <cstddef>
9 #include <cstdint>
10 #include <mutex>
11 #include <stack>
12 #include <thread>
13 #include <unordered_map>
14 #include <utility>
15 
16 #include <cuda_runtime_api.h>
17 
18 #include <cuda/memory_resource>
19 
20 #include <rmm/aligned.hpp>
21 #include <rmm/cuda_stream.hpp>
22 #include <rmm/error.hpp>
23 #include <rmm/resource_ref.hpp>
24 
25 #include <rapidsmpf/error.hpp>
26 #include <rapidsmpf/memory/scoped_memory_record.hpp>
27 #include <rapidsmpf/utils/misc.hpp>
28 
29 namespace rapidsmpf::detail {
30 
42 template <cuda::mr::resource_with<cuda::mr::device_accessible> PrimaryMR>
44  public:
50  // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape): false positive — primary_mr
51  // is moved into a heap-allocated control block inside make_shared_resource;
52  // the analyzer incorrectly traces the forwarding reference chain back to the
53  // outer caller's stack frame.
54  explicit RmmResourceAdaptorImpl( // NOLINT(clang-analyzer-core.StackAddressEscape)
55  PrimaryMR primary_mr
56  )
57  : primary_mr_{std::move(primary_mr)} {}
58 
59  // NOLINTEND(clang-analyzer-core.StackAddressEscape)
60 
70  template <typename... Args>
71  explicit RmmResourceAdaptorImpl(std::in_place_t, Args&&... args)
72  : primary_mr_{std::forward<Args>(args)...} {}
73 
74  ~RmmResourceAdaptorImpl() = default;
75 
78  RmmResourceAdaptorImpl& operator=(RmmResourceAdaptorImpl const&) = delete;
79  RmmResourceAdaptorImpl& operator=(RmmResourceAdaptorImpl&&) = delete;
80 
87  [[nodiscard]] bool operator==(RmmResourceAdaptorImpl const& other) const noexcept {
88  return this == std::addressof(other);
89  }
90 
95  [[nodiscard]] PrimaryMR const& get_upstream_resource() const noexcept {
96  return primary_mr_;
97  }
98 
100  [[nodiscard]] ScopedMemoryRecord get_main_record() const {
101  std::lock_guard<std::mutex> lock(mutex_);
102  return main_record_;
103  }
104 
106  [[nodiscard]] std::int64_t current_allocated() const noexcept {
107  std::lock_guard<std::mutex> lock(mutex_);
108  return main_record_.current();
109  }
110 
113  std::lock_guard<std::mutex> lock(mutex_);
114  record_stacks_[std::this_thread::get_id()].emplace();
115  }
116 
119  std::lock_guard lock(mutex_);
120  auto& stack = record_stacks_.at(std::this_thread::get_id());
121  RAPIDSMPF_EXPECTS(
122  !stack.empty(),
123  "calling end_scoped_memory_record() on an empty stack.",
124  std::out_of_range
125  );
126  auto ret = stack.top();
127  stack.pop();
128  if (!stack.empty()) {
129  stack.top().add_subscope(ret);
130  }
131  return ret;
132  }
133 
142  void* allocate(
143  cuda::stream_ref stream,
144  std::size_t bytes,
145  std::size_t alignment = rmm::CUDA_ALLOCATION_ALIGNMENT
146  ) {
147  void* ret = primary_mr_.allocate(stream, bytes, alignment);
148  std::lock_guard<std::mutex> lock(mutex_);
149  main_record_.record_allocation(safe_cast<std::int64_t>(bytes));
150  if (!record_stacks_.empty()) {
151  auto const thread_id = std::this_thread::get_id();
152  auto& record = record_stacks_[thread_id];
153  if (!record.empty()) {
154  record.top().record_allocation(safe_cast<std::int64_t>(bytes));
155  RAPIDSMPF_EXPECTS(
156  allocating_threads_.insert({ret, thread_id}).second,
157  "duplicate memory pointer"
158  );
159  }
160  }
161  return ret;
162  }
163 
173  cuda::stream_ref stream,
174  void* ptr,
175  std::size_t bytes,
176  std::size_t alignment = rmm::CUDA_ALLOCATION_ALIGNMENT
177  ) noexcept {
178  {
179  std::lock_guard<std::mutex> lock(mutex_);
180  main_record_.record_deallocation(safe_cast<std::int64_t>(bytes));
181  if (!allocating_threads_.empty()) {
182  auto const node = allocating_threads_.extract(ptr);
183  if (node) {
184  auto thread_id = node.mapped();
185  auto& record = record_stacks_[thread_id];
186  if (!record.empty()) {
187  record.top().record_deallocation(safe_cast<std::int64_t>(bytes));
188  }
189  }
190  }
191  }
192  primary_mr_.deallocate(stream, ptr, bytes, alignment);
193  }
194 
203  std::size_t bytes, std::size_t alignment = rmm::CUDA_ALLOCATION_ALIGNMENT
204  ) {
205  auto* ptr = allocate(sync_stream_, bytes, alignment);
206  sync_stream_.synchronize();
207  return ptr;
208  }
209 
218  void* ptr,
219  std::size_t bytes,
220  std::size_t alignment = rmm::CUDA_ALLOCATION_ALIGNMENT
221  ) noexcept {
222  deallocate(sync_stream_, ptr, bytes, alignment);
223  }
224 
226  friend void get_property(
227  RmmResourceAdaptorImpl const&, cuda::mr::device_accessible
228  ) noexcept {}
229 
230  private:
231  mutable std::mutex mutex_;
232  PrimaryMR primary_mr_;
233 
234  ScopedMemoryRecord main_record_;
235  std::unordered_map<std::thread::id, std::stack<ScopedMemoryRecord>> record_stacks_;
236  std::unordered_map<void*, std::thread::id> allocating_threads_;
237 
238  rmm::cuda_stream sync_stream_{
240  };
241 };
242 
243 } // namespace rapidsmpf::detail
Implementation class for RmmResourceAdaptor.
ScopedMemoryRecord get_main_record() const
Returns a copy of the main memory record.
void deallocate(cuda::stream_ref stream, void *ptr, std::size_t bytes, std::size_t alignment=rmm::CUDA_ALLOCATION_ALIGNMENT) noexcept
Deallocate memory asynchronously on the given stream.
bool operator==(RmmResourceAdaptorImpl const &other) const noexcept
Equality comparison.
void deallocate_sync(void *ptr, std::size_t bytes, std::size_t alignment=rmm::CUDA_ALLOCATION_ALIGNMENT) noexcept
Deallocate memory synchronously.
friend void get_property(RmmResourceAdaptorImpl const &, cuda::mr::device_accessible) noexcept
Tag this resource as device-accessible for the CCCL concept.
PrimaryMR const & get_upstream_resource() const noexcept
Returns a reference to the primary upstream resource.
void * allocate_sync(std::size_t bytes, std::size_t alignment=rmm::CUDA_ALLOCATION_ALIGNMENT)
Allocate memory synchronously.
void * allocate(cuda::stream_ref stream, std::size_t bytes, std::size_t alignment=rmm::CUDA_ALLOCATION_ALIGNMENT)
Allocate memory asynchronously on the given stream.
RmmResourceAdaptorImpl(PrimaryMR primary_mr)
Construct with a primary memory resource.
ScopedMemoryRecord end_scoped_memory_record()
End the current scoped memory record and return it.
void begin_scoped_memory_record()
Begin recording a new scoped memory usage record for the current thread.
RmmResourceAdaptorImpl(std::in_place_t, Args &&... args)
Construct the primary resource in-place from forwarded arguments.
std::int64_t current_allocated() const noexcept
Get the total current allocated memory through this resource.
void synchronize() const
static constexpr std::size_t CUDA_ALLOCATION_ALIGNMENT
Memory statistics for a specific scope.
std::int64_t current() const noexcept
Returns the current memory usage in bytes.
void record_allocation(std::int64_t nbytes)
Records a memory allocation event.
void record_deallocation(std::int64_t nbytes)
Records a memory deallocation event.