OpenVDB 13.1.0
Loading...
Searching...
No Matches
UnifiedBuffer.h
Go to the documentation of this file.
1// Copyright Contributors to the OpenVDB Project
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5 \file UnifiedBuffer.h
6
7 \author Ken Museth
8
9 \date October 15, 2024
10
11 \brief nanovdb::cuda::DualUnifiedBuffer that uses unified memory management
12
13 \note This file has no device-only kernel functions,
14 which explains why it's a .h and not .cuh file.
15*/
16
17#ifndef NANOVDB_CUDA_UNIFIEDBUFFER_H_HAS_BEEN_INCLUDED
18#define NANOVDB_CUDA_UNIFIEDBUFFER_H_HAS_BEEN_INCLUDED
19
20#include <cuda.h>
21#include <cassert> // for assert
22#include <initializer_list> // for std::initializer_list
23#include <memory>// for std::shared_ptr
24#include <nanovdb/HostBuffer.h>// for BufferTraits
25#include <nanovdb/util/cuda/Util.h>// for cudaCheck
26
27namespace nanovdb {// ================================================================
28
29namespace cuda {// ===================================================================
30
31/// @brief buffer, used for instance by the GridHandle, to allocate unified memory that
32/// can be resized and shared between multiple devices and the host.
33/// @note This is the implementation behind the deprecated UnifiedBuffer alias
34/// below, renamed so the [[deprecated]] attribute reaches only code
35/// that spells the public name: DistributedPointsToGrid's signature
36/// defaults reference this implementation, so default-using callers
37/// stay warning-free until the defaults change at removal. The Dual
38/// prefix marks the deprecated dual-accessor buffer family (this
39/// buffer satisfies that concept from a single managed allocation).
40/// Transitional -- do not adopt this name; it is deleted together
41/// with the alias. The header keeps its long-standing name and
42/// include path for the same reason: renaming a header breaks
43/// existing includes outright, and the old path is where external
44/// code will find the alias and its migration message.
46{
47 void *mPtr;
48 size_t mSize, mCapacity;
49public:
50
51 using PtrT = std::shared_ptr<DualUnifiedBuffer>;
52
53 /// @brief Default constructor of an empty buffer
54 DualUnifiedBuffer() : mPtr(nullptr), mSize(0), mCapacity(0){}
55
56 /// @brief Constructor that specifies both the size and capacity
57 /// @param size size of the buffer in bytes, indication what is actually used
58 /// @param capacity number of bytes in the virtual page table, i.e max size for growing
59 /// @note Capacity can be over-estimated to allow for future growth. Memory is not allocated
60 /// with this constructor, only a page table. Allocation happens on usage or when calling prefetch
61 DualUnifiedBuffer(size_t size, size_t capacity) : mPtr(nullptr), mSize(size), mCapacity(capacity)
62 {
63 assert(mSize <= mCapacity);
64 cudaCheck(cudaMallocManaged(&mPtr, mCapacity, cudaMemAttachGlobal));
65 }
66
67 /// @brief Similar to the constructor above except the size and capacity are equal, so no future growth is supported
69
70 /// @brief Constructor that specifies the size, capacity, and device (for prefetching)
71 /// @param size
72 /// @param capacity
73 /// @param device
74 /// @param stream
75 DualUnifiedBuffer(uint64_t size, uint64_t capacity, int device, cudaStream_t stream = 0) : mPtr(nullptr), mSize(size), mCapacity(size)
76 {
77 assert(mSize <= mCapacity);
78 cudaCheck(cudaMallocManaged(&mPtr, mCapacity, cudaMemAttachGlobal));
79 cudaCheck(util::cuda::memAdvise(mPtr, size, cudaMemAdviseSetPreferredLocation, device));
80 cudaCheck(util::cuda::memPrefetchAsync(mPtr, size, device, stream));
81 }
82
83 /// @brief Constructor with a specified device
84 /// @param size
85 /// @param device
86 /// @param stream
87 DualUnifiedBuffer(uint64_t size, int device, cudaStream_t stream = 0) : DualUnifiedBuffer(size, size, device, stream){}
88
89 /// @brief Disallow copy-construction
91
92 /// @brief Move copy-constructor
94 : mPtr(other.mPtr)
95 , mSize(other.mSize)
96 , mCapacity(other.mCapacity)
97 {
98 other.mPtr = nullptr;
99 other.mSize = other.mCapacity = 0;
100 }
101
102 /// @brief Destructor
103 ~DualUnifiedBuffer(){cudaCheck(cudaFree(mPtr));}
104
105 ///////////////////////////////////////////////////////////////////////
106
107 ///@{
108 /// @brief Factory methods that create an DualUnifiedBuffer instance and returns it with move semantics
111 ///@}
112
113 ///@{
114 /// @brief Factory methods that create a shared pointer to an DualUnifiedBuffer instance
115 static PtrT createPtr(size_t size, size_t capacity) {return std::make_shared<DualUnifiedBuffer>(size, capacity);}
116 static PtrT createPtr(size_t size) {return std::make_shared<DualUnifiedBuffer>(size);}
117 ///@}
118
119 /// @brief Legacy factory method that mirrors DeviceBuffer. It creates a DualUnifiedBuffer from a size and a reference buffer.
120 /// If a reference buffer is provided and its non-empty, it is used to defined the capacity of the new buffer
121 /// @param size Size on bytes of the new buffer
122 /// @param reference reference buffer optionally used to define the capacity
123 /// @param device Device whose preferred memory location is set for the new buffer
124 /// @param stream cuda stream
125 /// @return An instance of a new DualUnifiedBuffer using move semantics
126 static DualUnifiedBuffer create(size_t size, const DualUnifiedBuffer* reference, int device, cudaStream_t stream)
127 {
128 const size_t capacity = (reference && reference->capacity()) ? reference->capacity() : size;
130 cudaCheck(util::cuda::memAdvise(buffer.mPtr, size, cudaMemAdviseSetPreferredLocation, device));
131 cudaCheck(util::cuda::memPrefetchAsync(buffer.mPtr, size, device, stream));
132 return buffer;
133 }
134
135 /// @brief Factory method that created a buffer on the host of the specified size. If the
136 /// reference buffer has a capacity it is used. Also the buffer is prefetched to the host
137 /// @param size byte size of buffer initiated on the host
138 /// @param reference optional reference buffer from which the capacity is derived
139 static DualUnifiedBuffer create(size_t size, const DualUnifiedBuffer* reference){return create(size, reference, cudaCpuDeviceId, (cudaStream_t)0);}
140
141 /// @brief Factory method that created a buffer on the host or device of the specified size. If the
142 /// reference buffer has a capacity it is used. Also the buffer is prefetched to the host or (current) device
143 /// @param size byte size of buffer initiated on the device or host
144 /// @param reference optional reference buffer from which the capacity is derived
145 /// @param host If true the buffer will be prefetched to the host, else to the current device
146 /// @param stream optional cuda stream
147 static DualUnifiedBuffer create(size_t size, const DualUnifiedBuffer* reference, bool host, void* stream = nullptr)
148 {
149 int device = cudaCpuDeviceId;
150 if (!host) cudaGetDevice(&device);
151 return create(size, reference, device, (cudaStream_t)stream);
152 }
153
154 /// @brief Free all memory and reset this instance to empty
155 void clear()
156 {
157 cudaCheck(cudaFree(mPtr));
158 mPtr = nullptr;
159 mSize = mCapacity = 0;
160 }
161
162 /// @brief Disallow copy assignment operation
164
165 /// @brief Allow move assignment operation
167 {
168 cudaCheck(cudaFree(mPtr));
169 mPtr = other.mPtr;
170 mSize = other.mSize;
171 mCapacity = other.mCapacity;
172 other.mPtr = nullptr;
173 other.mSize = other.mCapacity = 0;
174 return *this;
175 }
176
177 /// @brief initialize buffer as a new with the specified size and capacity
178 /// @param size size of memory block to be used in bytes
179 /// @param capacity size of page table in bytes
180 void init(size_t size, size_t capacity)
181 {
183 cudaCheck(cudaFree(mPtr));
184 mSize = size;
185 mCapacity = capacity;
186 cudaCheck(cudaMallocManaged(&mPtr, capacity, cudaMemAttachGlobal));
187 }
188
189 /// @brief Resize the memory block managed by this buffer. If the current capacity is larger than the new size this method
190 /// simply redefines size. Otherwise a new page-table is defined, with the specified advice, and the old block is copied to the new block.
191 /// @param size size of the new memory block
192 /// @param dev the device ID on which to apply each advice provided in list, cudaCpuDeviceId = -1, 0, 1, ...
193 /// @param list advices to be applied to the resized range
194 void resize(size_t size, int dev = cudaCpuDeviceId, std::initializer_list<cudaMemoryAdvise> list = {cudaMemAdviseSetPreferredLocation})
195 {
196 if (size <= mCapacity) {
197 mSize = size;
198 } else {
199 void *ptr = 0;
200 cudaCheck(cudaMallocManaged(&ptr, size, cudaMemAttachGlobal));
201 if (dev > -2) for (auto a : list) cudaCheck(util::cuda::memAdvise(ptr, size, a, dev));
202 if (mSize > 0) {// copy over data from the old memory block
203 cudaCheck(cudaMemcpy(ptr, mPtr, std::min(mSize, size), cudaMemcpyDefault));
204 cudaCheck(cudaFree(mPtr));
205 }
206 mPtr = ptr;
207 mSize = mCapacity = size;
208 }
209 }
210
211 /// @brief Apply a single advise to a memory block
212 /// @param byteOffset offset in bytes marking the beginning of the memory block to be advised
213 /// @param size size in bytes of the memory block to be advised.
214 /// @param dev the device ID on which to apply the advice provided in adv, cudaCpuDeviceId = -1, 0, 1, ...
215 /// @param adv advice to be applied to the resized range
216 void advise(ptrdiff_t byteOffset, size_t size, int dev, cudaMemoryAdvise adv) const
217 {
218 cudaCheck(util::cuda::memAdvise(util::PtrAdd(mPtr, byteOffset), size, adv, dev));
219 }
220
221 /// @brief Apply a list of advices to a memory block
222 /// @param byteOffset offset in bytes marking the beginning of the memory block to be advised
223 /// @param size size in bytes of the memory block to be advised.
224 /// @param dev the device ID to prefetch to, cudaCpuDeviceId = -1, 0, 1, ...
225 /// @param list list of cuda advises
226 void advise(ptrdiff_t byteOffset, size_t size, int dev, std::initializer_list<cudaMemoryAdvise> list) const
227 {
228 void *ptr = util::PtrAdd(mPtr, byteOffset);
229 for (auto a : list) cudaCheck(util::cuda::memAdvise(ptr, size, a, dev));
230 }
231
232 /// @brief Prefetches data to the specified device, i.e. ensure the device has an up-to-date copy of the memory specified
233 /// @param byteOffset offset in bytes marking the beginning of the memory block to be prefetched
234 /// @param size size in bytes of the memory block to be prefetched. The default value of zero means copy all @c this->size() bytes.
235 /// @param dev the device ID to prefetch to, cudaCpuDeviceId = -1, 0, 1, ...
236 /// @param stream cuda stream
237 void prefetch(ptrdiff_t byteOffset = 0, size_t size = 0, int dev = cudaCpuDeviceId, cudaStream_t stream = cudaStreamPerThread) const
238 {
239 cudaCheck(util::cuda::memPrefetchAsync(util::PtrAdd(mPtr, byteOffset), size ? size : mSize, dev, stream));
240 }
241
242 ///////////////////////////////////////////////////////////////////////
243
244 /// @brief Prefetches all data to the specified device
245 /// @param device device ID, cudaCpuDeviceId = -1, 0, 1, ...
246 /// @param stream cuda stream
247 /// @param sync if false the memory copy is asynchronous
248 /// @note Legacy method included for compatibility with DeviceBuffer
249 void deviceUpload(int device = 0, cudaStream_t stream = cudaStreamPerThread, bool sync = false) const
250 {
251 cudaCheck(util::cuda::memPrefetchAsync(mPtr, mSize, device, stream));
252 if (sync) cudaCheck(cudaStreamSynchronize(stream));
253 }
254 void deviceUpload(int device, void* stream, bool sync) const{this->deviceUpload(device, cudaStream_t(stream));}
255
256 /// @brief Prefetches all data to the current device, as given by cudaGetDevice
257 /// @param stream cuda stream
258 /// @param sync if false the memory copy is asynchronous
259 /// @note Legacy method included for compatibility with DeviceBuffer
260 void deviceUpload(void* stream, bool sync) const{
261 int device = 0;
262 cudaCheck(cudaGetDevice(&device));
263 this->deviceUpload(device, cudaStream_t(stream), sync);
264 }
265
266 ///////////////////////////////////////////////////////////////////////
267
268 /// @brief Prefetches all data to the host
269 /// @param stream cuda stream
270 /// @param sync if false the memory copy is asynchronous
271 void deviceDownload(cudaStream_t stream = 0, bool sync = false) const
272 {
273 cudaCheck(util::cuda::memPrefetchAsync(mPtr, mSize, cudaCpuDeviceId, stream));
274 if (sync) cudaCheck(cudaStreamSynchronize(stream));
275 }
276
277 /// @brief Legacy
278 /// @param stream
279 /// @param sync
280 void deviceDownload(void* stream, bool sync) const{this->deviceDownload(cudaStream_t(stream), sync);}
281
282 // used by GridHandle
283 void deviceDownload(int dummmy, void* stream, bool sync) const{this->deviceDownload(cudaStream_t(stream), sync);}
284
285 ///////////////////////////////////////////////////////////////////////
286
287 /// @brief Returns a raw pointer to the unified memory managed by this instance.
288 /// @warning Note that the pointer can be NULL!
289 void* data() const {return mPtr;}
290
291 /// @brief Returns an offset pointer of a specific type from the allocated unified memory
292 /// @tparam T Type of the pointer returned
293 /// @param count Numbers of elements of @c parameter type T to skip (or offset) the return pointer
294 /// @warning assumes that this instance is not empty!
295 template <typename T>
296 T* data(ptrdiff_t count = 0) const {
297 NANOVDB_ASSERT(mPtr != nullptr || count == 0);
298 return reinterpret_cast<T*>(mPtr) + count;
299 }
300
301 /// @brief Returns a byte offset void pointer from the unified memory
302 /// @param byteOffset Number of bytes to skip (or offset) the return pointer
303 /// @warning assumes that this instance is not empty!
304 void* data(ptrdiff_t byteOffset) const {
305 NANOVDB_ASSERT(mPtr != nullptr || byteOffset == 0);
306 return util::PtrAdd(mPtr, byteOffset);
307 }
308
309 /// @brief Legacy
310 /// @return
311 void* deviceData() const {return mPtr;}
312 void* deviceData(int) const {return mPtr;}
313
314 /// @brief Size of the allocated pages in this instance
315 /// @return number bytes allocated by this instance
316 size_t size() const {return mSize;}
317
318 /// @brief Capacity of this instance, i.e. room in page table
319 /// @return number of bytes reserved, but not necessarily allocated, by this instance
320 size_t capacity() const {return mCapacity;}
321
322 //@{
323 /// @brief Returns true if this allocator is empty, i.e. has no allocated memory
324 inline bool empty() const { return mPtr == nullptr; }
325 inline bool isEmpty() const { return this->empty(); }
326 //@}
327
328};// DualUnifiedBuffer
329
330/// @brief The managed-memory buffer under its long-standing public name.
331/// @deprecated Managed grid storage is moving to the single-space
332/// cuda::Buffer over cuda::ManagedResource, whose GridHandle
333/// exposes the same host and device accessors from the one
334/// managed allocation: pass cuda::Buffer<std::byte,
335/// cuda::ManagedResource> to the multi-GPU builders, or build
336/// into a host handle and move it with cuda::copyTo (see
337/// cuda/HandleStorage.h). For a standalone managed allocation,
338/// use cuda::Buffer<T, cuda::ManagedResource> with explicit
339/// util::cuda::memAdvise / memPrefetchAsync calls in place of
340/// this class's advise and prefetch members (the multi-GPU
341/// examples show both patterns). This buffer and the name are
342/// removed together after a deprecation window.
343using UnifiedBuffer [[deprecated("managed grid storage is moving to cuda::Buffer<std::byte, cuda::ManagedResource> (cuda/Buffer.h): pass it to the multi-GPU builders, or build into a host handle and use cuda::copyTo (cuda/HandleStorage.h); see the multi-GPU examples")]] = DualUnifiedBuffer;
344
345}// namespace cuda
346
347template<>
348struct BufferTraits<cuda::DualUnifiedBuffer>
349{
350 static constexpr bool hasDeviceDual = true;
351};
352
353}// namespace nanovdb
354
355#endif // end of NANOVDB_CUDA_UNIFIEDBUFFER_H_HAS_BEEN_INCLUDED
HostBuffer - a buffer that contains a shared or private bump pool to either externally or internally ...
void prefetch(ptrdiff_t byteOffset=0, size_t size=0, int dev=cudaCpuDeviceId, cudaStream_t stream=cudaStreamPerThread) const
Prefetches data to the specified device, i.e. ensure the device has an up-to-date copy of the memory ...
Definition UnifiedBuffer.h:237
static DualUnifiedBuffer create(size_t size, const DualUnifiedBuffer *reference, bool host, void *stream=nullptr)
Factory method that created a buffer on the host or device of the specified size. If the reference bu...
Definition UnifiedBuffer.h:147
DualUnifiedBuffer()
Default constructor of an empty buffer.
Definition UnifiedBuffer.h:54
DualUnifiedBuffer & operator=(const DualUnifiedBuffer &)=delete
Disallow copy assignment operation.
size_t size() const
Size of the allocated pages in this instance.
Definition UnifiedBuffer.h:316
static PtrT createPtr(size_t size)
Factory methods that create a shared pointer to an DualUnifiedBuffer instance.
Definition UnifiedBuffer.h:116
std::shared_ptr< DualUnifiedBuffer > PtrT
Definition UnifiedBuffer.h:51
T * data(ptrdiff_t count=0) const
Returns an offset pointer of a specific type from the allocated unified memory.
Definition UnifiedBuffer.h:296
DualUnifiedBuffer(const DualUnifiedBuffer &)=delete
Disallow copy-construction.
DualUnifiedBuffer(uint64_t size, int device, cudaStream_t stream=0)
Constructor with a specified device.
Definition UnifiedBuffer.h:87
DualUnifiedBuffer(DualUnifiedBuffer &&other) noexcept
Move copy-constructor.
Definition UnifiedBuffer.h:93
void deviceUpload(int device, void *stream, bool sync) const
Definition UnifiedBuffer.h:254
void * deviceData() const
Legacy.
Definition UnifiedBuffer.h:311
void deviceDownload(cudaStream_t stream=0, bool sync=false) const
Prefetches all data to the host.
Definition UnifiedBuffer.h:271
bool empty() const
Returns true if this allocator is empty, i.e. has no allocated memory.
Definition UnifiedBuffer.h:324
void deviceDownload(int dummmy, void *stream, bool sync) const
Definition UnifiedBuffer.h:283
void * deviceData(int) const
Definition UnifiedBuffer.h:312
DualUnifiedBuffer & operator=(DualUnifiedBuffer &&other)
Allow move assignment operation.
Definition UnifiedBuffer.h:166
DualUnifiedBuffer(size_t size, size_t capacity)
Constructor that specifies both the size and capacity.
Definition UnifiedBuffer.h:61
size_t capacity() const
Capacity of this instance, i.e. room in page table.
Definition UnifiedBuffer.h:320
DualUnifiedBuffer(uint64_t size, uint64_t capacity, int device, cudaStream_t stream=0)
Constructor that specifies the size, capacity, and device (for prefetching)
Definition UnifiedBuffer.h:75
void deviceUpload(void *stream, bool sync) const
Prefetches all data to the current device, as given by cudaGetDevice.
Definition UnifiedBuffer.h:260
DualUnifiedBuffer(size_t size)
Similar to the constructor above except the size and capacity are equal, so no future growth is suppo...
Definition UnifiedBuffer.h:68
void deviceDownload(void *stream, bool sync) const
Legacy.
Definition UnifiedBuffer.h:280
void * data() const
Returns a raw pointer to the unified memory managed by this instance.
Definition UnifiedBuffer.h:289
~DualUnifiedBuffer()
Destructor.
Definition UnifiedBuffer.h:103
void init(size_t size, size_t capacity)
initialize buffer as a new with the specified size and capacity
Definition UnifiedBuffer.h:180
static DualUnifiedBuffer create(size_t size)
Factory methods that create an DualUnifiedBuffer instance and returns it with move semantics.
Definition UnifiedBuffer.h:110
void deviceUpload(int device=0, cudaStream_t stream=cudaStreamPerThread, bool sync=false) const
Prefetches all data to the specified device.
Definition UnifiedBuffer.h:249
void resize(size_t size, int dev=cudaCpuDeviceId, std::initializer_list< cudaMemoryAdvise > list={cudaMemAdviseSetPreferredLocation})
Resize the memory block managed by this buffer. If the current capacity is larger than the new size t...
Definition UnifiedBuffer.h:194
void advise(ptrdiff_t byteOffset, size_t size, int dev, cudaMemoryAdvise adv) const
Apply a single advise to a memory block.
Definition UnifiedBuffer.h:216
static DualUnifiedBuffer create(size_t size, const DualUnifiedBuffer *reference)
Factory method that created a buffer on the host of the specified size. If the reference buffer has a...
Definition UnifiedBuffer.h:139
void clear()
Free all memory and reset this instance to empty.
Definition UnifiedBuffer.h:155
bool isEmpty() const
Definition UnifiedBuffer.h:325
void * data(ptrdiff_t byteOffset) const
Returns a byte offset void pointer from the unified memory.
Definition UnifiedBuffer.h:304
static PtrT createPtr(size_t size, size_t capacity)
Factory methods that create a shared pointer to an DualUnifiedBuffer instance.
Definition UnifiedBuffer.h:115
static DualUnifiedBuffer create(size_t size, size_t capacity)
Factory methods that create an DualUnifiedBuffer instance and returns it with move semantics.
Definition UnifiedBuffer.h:109
void advise(ptrdiff_t byteOffset, size_t size, int dev, std::initializer_list< cudaMemoryAdvise > list) const
Apply a list of advices to a memory block.
Definition UnifiedBuffer.h:226
static DualUnifiedBuffer create(size_t size, const DualUnifiedBuffer *reference, int device, cudaStream_t stream)
Legacy factory method that mirrors DeviceBuffer. It creates a DualUnifiedBuffer from a size and a ref...
Definition UnifiedBuffer.h:126
Definition GridHandle.h:37
cudaError_t memPrefetchAsync(const void *devPtr, size_t count, int dstDevice, cudaStream_t stream)
Compatbility wrapper for cudaMemPrefetchAsync/cudaMemPrefetchAsync.
Definition Util.h:271
cudaError_t memAdvise(const void *devPtr, size_t count, cudaMemoryAdvise advice, int device)
Compatbility wrapper for cudaMemAdvise/cudaMemAdvise.
Definition Util.h:266
static DstT * PtrAdd(void *p, int64_t offset)
Adds a byte offset to a non-const pointer to produce another non-const pointer.
Definition Util.h:524
Defines a simple memory pool used to call cub functions that use dynamic temporary storage.
Definition GridHandle.h:31
#define NANOVDB_ASSERT(x)
Definition Util.h:53
Cuda specific utility functions.
#define cudaCheck(ans)
Definition Util.h:49
static constexpr bool hasDeviceDual
Definition UnifiedBuffer.h:350
Definition HostBuffer.h:101