OpenVDB 13.1.0
Loading...
Searching...
No Matches
DeviceBuffer.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 DeviceBuffer.h
6
7 \author Ken Museth
8
9 \date January 8, 2020
10
11 \brief DualDeviceBuffer has one pinned host buffer and multiple device CUDA buffers
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_DEVICEBUFFER_H_HAS_BEEN_INCLUDED
18#define NANOVDB_CUDA_DEVICEBUFFER_H_HAS_BEEN_INCLUDED
19
20#include <cuda.h>
21#include <memory>// for std::shared_ptr
22#include <nanovdb/HostBuffer.h>// for BufferTraits
23#include <nanovdb/util/cuda/Util.h>// for cudaMalloc/cudaMallocManaged/cudaFree
24
25namespace nanovdb {// ================================================================
26
27namespace cuda {// ===================================================================
28
29// ----------------------------> DualDeviceBuffer <--------------------------------------
30
31/// @brief Simple memory buffer using un-managed pinned host memory when compiled with NVCC.
32/// Obviously this class is making explicit used of CUDA so replace it with your own memory
33/// allocator if you are not using CUDA.
34/// @note While CUDA's pinned host memory allows for asynchronous memory copy between host and device
35/// it is significantly slower then cached (un-pinned) memory on the host.
36/// @note This is the implementation behind the deprecated DeviceBuffer alias
37/// below, renamed so the [[deprecated]] attribute reaches only code
38/// that spells the public name: the GPU tools' signature defaults
39/// reference this implementation, so default-using callers stay
40/// warning-free until the defaults change at removal. Transitional --
41/// do not adopt this name; it is deleted together with the alias. The
42/// header keeps its long-standing name and include path for the same
43/// reason: renaming a header breaks existing includes outright, and
44/// the old path is where external code will find the alias and its
45/// migration message.
47{
48 uint64_t mSize; // total number of bytes managed by this buffer (assumed to be identical for host and device)
49 void *mCpuData, **mGpuData; // raw pointers to the host and device buffers
50 int mDeviceCount, mManaged;// if mManaged is non-zero this class is responsible for allocating and freeing memory buffers. Otherwise this is assumed to be handled externally
51 cudaEvent_t *mEvents = nullptr;// per-device event marking the last use of each managed device buffer (parallel to mGpuData, length mDeviceCount). Every use waits on this event before issuing work and re-records it afterwards, so the single event transitively covers EVERY stream the buffer has been used on. Frees then wait on it, which orders them after all outstanding work: freeing on the default stream alone is only safe for blocking streams, and freeing on the last-used stream alone is only safe when just one stream was used.
52
53 /// @brief Initialize buffer
54 /// @param size byte size of buffer to be initialized
55 /// @param device id of the device on which to initialize the buffer
56 /// @note All existing buffers are first cleared
57 /// @warning size is expected to be non-zero. Use clear() clear buffer!
58 void init(uint64_t size, int device, cudaStream_t stream);
59
60 /// @brief Free every managed device allocation, each ordered after all tracked uses of the
61 /// buffer, and destroy the tracking events.
62 /// @param stream Stream the frees are issued on for allocations owned by the CURRENT device.
63 /// A stream belongs to a single device, so it cannot carry frees for other devices'
64 /// memory pools; allocations on other devices are freed on their own device's default
65 /// stream, after switching to that device.
66 /// @note Destroying an event with a pending wait is safe: CUDA releases it once the device
67 /// has completed it.
68 void freeDualDeviceBuffers(cudaStream_t stream)
69 {
70 int current = 0;
71 cudaCheck(cudaGetDevice(&current));
72 for (int i = 0; i < mDeviceCount; ++i) {
73 if (mGpuData[i]) {
74 const cudaStream_t freeStream = (i == current) ? stream : cudaStream_t{0};
75 if (i != current) cudaCheck(cudaSetDevice(i));
76 this->orderAfterPriorUses(i, freeStream);
77 cudaCheck(util::cuda::freeAsync(mGpuData[i], freeStream));
78 if (i != current) cudaCheck(cudaSetDevice(current));
79 }
80 if (mEvents && mEvents[i]) {
81 cudaCheck(cudaEventDestroy(mEvents[i]));
82 mEvents[i] = nullptr;
83 }
84 }
85 }
86
87public:
88
89 using PtrT = std::shared_ptr<DualDeviceBuffer>;
90
91 /// @brief Default constructor of an empty buffer
92 DualDeviceBuffer() : mSize(0), mCpuData(nullptr), mGpuData(nullptr), mDeviceCount(0), mManaged(0){}
93
94 /// @brief Constructor with a specified device and size
95 /// @param size byte size of buffer to be initialized
96 /// @param device id of the device on which to initialize the buffer
97 /// @param stream cuda stream
98 DualDeviceBuffer(uint64_t size, int device = cudaCpuDeviceId, cudaStream_t stream = 0) : DualDeviceBuffer()
99 {
100 this->init(size, device, stream);
101 }
102
103 /// @brief Constructor
104 /// @param size byte size of buffer to be initialized
105 /// @param host If true buffer is initialized only on the host/CPU, else on the current device/GPU
106 /// @param stream optional stream argument (defaults to stream NULL)
107 DualDeviceBuffer(uint64_t size, bool host, void* stream) : DualDeviceBuffer()
108 {
109 int device = cudaCpuDeviceId;
110 if (!host) cudaCheck(cudaGetDevice(&device));
111 this->init(size, device, reinterpret_cast<cudaStream_t>(stream));
112 }
113
114 /// @brief Constructor for externally managed host and device buffers
115 /// @param size byte size of the two external buffers
116 /// @param cpuData host buffer, assumed to NOT be NULL
117 /// @param gpuData device buffer, assumed to NOT be NULL;
118 /// @note The device buffer, @c gpuData, will be associated
119 /// with the current device ID given by cudaGetDevice
120 DualDeviceBuffer(uint64_t size, void* cpuData, void* gpuData)
121 : mSize(size)
122 , mCpuData(cpuData)
123 , mManaged(0)
124 {
125 cudaCheck(cudaGetDeviceCount(&mDeviceCount));
126 mGpuData = new void*[mDeviceCount]();// NULL initialization
127 NANOVDB_ASSERT(cpuData);
128 NANOVDB_ASSERT(gpuData);
129 int device = 0;
130 cudaCheck(cudaGetDevice(&device));
131 mGpuData[device] = gpuData;
132 }
133
134 /// @brief Constructor for externally managed host and multiple device buffers
135 /// @param size byte size of the two external buffers
136 /// @param cpuData host buffer, assumed to NOT be NULL
137 /// @param list list of device IDs and external device buffers, all assumed to not be NULL
138 DualDeviceBuffer(uint64_t size, void* cpuData, std::initializer_list<std::pair<int,void*>> list)
139 : mSize(size)
140 , mCpuData(cpuData)
141 , mManaged(0)
142 {
143 NANOVDB_ASSERT(cpuData);
144 cudaCheck(cudaGetDeviceCount(&mDeviceCount));
145 mGpuData = new void*[mDeviceCount]();// NULL initialization
146 for (auto &p : list) {
147 NANOVDB_ASSERT(p.first>=0 && p.first<mDeviceCount);
148 NANOVDB_ASSERT(p.second);
149 mGpuData[p.first] = p.second;
150 }
151 }
152
153 /// @brief Disallow copy-construction
155
156 /// @brief Move copy-constructor
158 : mSize(other.mSize)
159 , mCpuData(other.mCpuData)
160 , mGpuData(other.mGpuData)
161 , mDeviceCount(other.mDeviceCount)
162 , mManaged(other.mManaged)
163 , mEvents(other.mEvents)
164 {
165 other.mCpuData = other.mGpuData = nullptr;
166 other.mEvents = nullptr;
167 other.mSize = other.mDeviceCount = other.mManaged = 0;
168 }
169
170 /// @brief Copy-constructor from a HostBuffer
171 /// @param buffer host buffer from which to copy data
172 /// @param device id of the device on which to initialize the buffer
173 /// @param stream cuda stream
174 DualDeviceBuffer(const HostBuffer& buffer, int device = cudaCpuDeviceId, cudaStream_t stream = 0)
175 : DualDeviceBuffer(buffer.size(), device, stream)
176 {
177 if (mCpuData) {
178 cudaCheck(cudaMemcpy(mCpuData, buffer.data(), mSize, cudaMemcpyHostToHost));
179 } else if (mGpuData[device]) {
180 cudaCheck(cudaMemcpyAsync(mGpuData[device], buffer.data(), mSize, cudaMemcpyHostToDevice, stream));
181 }
182 }
183
184 /// @brief Destructor frees memory on both the host and device
185 /// @note Each managed device free waits on that device's tracking event first, so it is
186 /// ordered after every stream the buffer was used on, not just the most recent one.
187 ~DualDeviceBuffer() { this->clear(); };
188
189 /// @brief Static factory method that return an instance of this buffer
190 /// @param size byte size of buffer to be initialized
191 /// @param dummy this argument is currently ignored but required to match the API of the HostBuffer
192 /// @param host If true buffer is initialized only on the host/CPU, else only on the device/GPU
193 /// @param stream optional stream argument (defaults to stream NULL)
194 /// @return An instance of this class using move semantics
195 static DualDeviceBuffer create(uint64_t size, const DualDeviceBuffer* dummy, bool host, void* stream){return DualDeviceBuffer(size, host, stream);}
196
197 /// @brief Static factory method that returns an instance of this buffer
198 /// @param size byte size of buffer to be initialized
199 /// @param dummy this argument is currently ignored but required to match the API of the HostBuffer
200 /// @param device id of the device on which to initialize the buffer
201 /// @param stream cuda stream
202 static DualDeviceBuffer create(uint64_t size, const DualDeviceBuffer* dummy = nullptr, int device = cudaCpuDeviceId, cudaStream_t stream = 0){return DualDeviceBuffer(size, device, stream);}
203
204 /// @brief Static factory method that returns an instance of this buffer that wraps externally managed memory
205 /// @param size byte size of buffer specified by external memory
206 /// @param cpuData pointer to externally managed host memory
207 /// @param gpuData pointer to externally managed device memory
208 /// @return An instance of this class using move semantics
209 static DualDeviceBuffer create(uint64_t size, void* cpuData, void* gpuData) {return DualDeviceBuffer(size, cpuData, gpuData);}
210
211 /// @brief Static factory method that returns an instance of this buffer that wraps externally managed host and device memory
212 /// @param size byte size of buffer to be initialized
213 /// @param cpuData pointer to externally managed host memory
214 /// @param list list of device IDs and device memory pointers
215 static DualDeviceBuffer create(uint64_t size, void* cpuData, std::initializer_list<std::pair<int,void*>> list) {return DualDeviceBuffer(size, cpuData, list);}
216
217 /// @brief Static factory method that returns an instance of this buffer constructed from a HostBuffer
218 /// @param buffer host buffer from which to copy data
219 /// @param device id of the device on which to initialize the buffer
220 /// @param stream cuda stream
221 static DualDeviceBuffer create(const HostBuffer& buffer, int device = cudaCpuDeviceId, cudaStream_t stream = 0) {return DualDeviceBuffer(buffer, device, stream);}
222
223 ///////////////////////////////////////////////////////////////////////
224
225 /// @{
226 /// @brief Factory methods that create a shared pointer to an DualDeviceBuffer instance
227 static PtrT createPtr(uint64_t size, const DualDeviceBuffer* = nullptr, int device = cudaCpuDeviceId, cudaStream_t stream = 0) {return std::make_shared<DualDeviceBuffer>(size, device, stream);}
228 static PtrT createPtr(uint64_t size, void* cpuData, void* gpuData) {return std::make_shared<DualDeviceBuffer>(size, cpuData, gpuData);}
229 static PtrT createPtr(uint64_t size, void* cpuData, std::initializer_list<std::pair<int,void*>> list) {return std::make_shared<DualDeviceBuffer>(size, cpuData, list);}
230 static PtrT createPtr(const HostBuffer& buffer, int device = cudaCpuDeviceId, cudaStream_t stream = 0) {return std::make_shared<DualDeviceBuffer>(buffer, device, stream);}
231 /// @}
232
233 ///////////////////////////////////////////////////////////////////////
234
235 /// @brief Disallow copy assignment operation
237
238 /// @brief Move copy assignment operation
240
241 ///////////////////////////////////////////////////////////////////////
242
243 /// @brief Retuns a raw void pointer to the host/CPU buffer managed by this allocator.
244 /// @warning Note that the pointer can be NULL!
245 void* data() const { return mCpuData; }
246
247 /// @brief Returns an offset pointer of a specific type from the allocated host memory
248 /// @tparam T Type of the pointer returned
249 /// @param count Numbers of elements of @c parameter type T to skip
250 /// @param device Device whose buffer is returned, or cudaCpuDeviceId for the host buffer
251 /// @warning might return NULL
252 template <typename T>
253 T* data(ptrdiff_t count = 0, int device = cudaCpuDeviceId) const
254 {
255 NANOVDB_ASSERT(device >= cudaCpuDeviceId && device < mDeviceCount);
256 void *ptr = device == cudaCpuDeviceId ? mCpuData : mGpuData[device];
257 return ptr ? reinterpret_cast<T*>(ptr) + count : nullptr;
258 }
259
260 /// @brief Returns a byte offset void pointer from the allocated host memory
261 /// @param byteOffset offset of return pointer in units of bytes
262 /// @param device Device whose buffer is returned, or cudaCpuDeviceId for the host buffer
263 /// @warning assumes that this instance is not empty!
264 void* data(ptrdiff_t byteOffset, int device = cudaCpuDeviceId) const
265 {
266 NANOVDB_ASSERT(device >= cudaCpuDeviceId && device < mDeviceCount);
267 void *ptr = device == cudaCpuDeviceId ? mCpuData : mGpuData[device];
268 return ptr ? reinterpret_cast<char*>(ptr) + byteOffset : nullptr;
269 }
270
271 ///////////////////////////////////////////////////////////////////////
272
273 /// @brief Order work subsequently issued on @a stream after every prior use of this
274 /// device buffer, whichever stream those uses were issued on. The consume-side
275 /// companion of recordUse: an external consumer (e.g. a zero-copy array-interface
276 /// export) calls this with its own stream before reading, so it cannot observe a
277 /// partially-written buffer after asynchronous uploads or recorded kernels.
278 /// @param device Device whose buffer is about to be read
279 /// @param stream Stream the consumer's work will be issued on
280 void orderAfterPriorUses(int device, cudaStream_t stream) const
281 {
282 if (mEvents && mEvents[device]) cudaCheck(cudaStreamWaitEvent(stream, mEvents[device], 0));
283 }
284
285 /// @brief Record that this buffer's device data was just used on @a stream, so that the
286 /// buffer's device frees (destructor, move-assignment, clear) are ordered after that
287 /// work. Uses issued through deviceUpload/deviceDownload are recorded automatically;
288 /// callers that enqueue their own kernels or copies against the raw pointer returned
289 /// by deviceData() should call this afterwards. Without it, such work is only safe if
290 /// it is on a blocking stream (which the free, issued on the default stream, waits on
291 /// implicitly) or if the caller synchronizes before the buffer is cleared/destroyed.
292 /// @param device Device whose buffer was used
293 /// @param stream Stream the work was issued on
294 /// @note Recording chains across streams: @a stream is first ordered after the previously
295 /// recorded use (if any) so the single per-device event transitively covers every
296 /// recorded use, not just the last one. Without this, concurrent uses on streams A
297 /// then B would leave only B's event, and the device free could run while A's work
298 /// is still in flight. The side effect is that work subsequently issued on @a stream
299 /// also waits on the previously recorded use -- acceptable for a shared buffer, where
300 /// later-recorded consumers observing earlier writes is the expected ordering. Note
301 /// this also serializes CONCURRENT READERS that record uses (the single event cannot
302 /// distinguish read-read from write-read); if that ever matters in a profile, the
303 /// upgrade path is a read/write-separated or per-record event scheme, not a revert.
304 void recordUse(int device, cudaStream_t stream)
305 {
306 if (!mEvents) return;
307 if (mEvents[device] == nullptr) {// events are per-device, so create it on the right one
308 int current = 0;
309 cudaCheck(cudaGetDevice(&current));
310 if (current != device) cudaCheck(cudaSetDevice(device));
311 cudaCheck(cudaEventCreateWithFlags(&mEvents[device], cudaEventDisableTiming));
312 if (current != device) cudaCheck(cudaSetDevice(current));
313 } else {
314 // Re-recording MOVES the event; chain first so the new capture also covers the
315 // prior recorded use (waiting on a never-recorded or completed event is a no-op).
316 cudaCheck(cudaStreamWaitEvent(stream, mEvents[device], 0));
317 }
318 cudaCheck(cudaEventRecord(mEvents[device], stream));
319 }
320
321 /// @brief Retuns a raw pointer to the specified device/GPU buffer managed by this allocator.
322 /// @warning Note that the pointer can be NULL!
323 /// @note Work enqueued against this raw pointer is invisible to the buffer's lifetime
324 /// tracking: on a non-blocking stream, call recordUse afterwards (or synchronize
325 /// before the buffer is cleared/destroyed) so the device free is ordered after it.
326 void* deviceData(int device) const {
327 NANOVDB_ASSERT(device >= 0 && device < mDeviceCount);
328 return mGpuData[device];
329 }
330
331 /// @brief Retuns a raw pointer to the current device/GPU buffer managed by this allocator.
332 /// @warning Note that the pointer can be NULL!
333 void* deviceData() const {
334 int device = cudaCpuDeviceId;
335 cudaCheck(cudaGetDevice(&device));
336 return this->deviceData(device);
337 }
338
339 ///////////////////////////////////////////////////////////////////////
340
341 /// @brief Uploads buffer on the host to a specific device. If it doesn't exist it's created first.
342 /// @param device Device ID that the data is copied to
343 /// @param stream cuda stream
344 /// @param sync if false the memory copy is asynchronous.
345 /// @warning Assumes that the host buffer already exists!
346 /// @note determine the current device with cudaGetDevice
347 void deviceUpload(int device = 0, cudaStream_t stream = 0, bool sync = true);
348 void deviceUpload(int device, void* stream, bool sync){this->deviceUpload(device, cudaStream_t(stream), sync);}
349
350 /// @brief Upload buffer from the host to ALL the existing devices, i.e. CPU -> GPU.
351 /// If no device buffers exist one is created for the current device (typically 0)
352 /// and subsequently populated with the host data.
353 /// @param stream CUDA stream.
354 /// @param sync if false the memory copy is asynchronous.
355 /// @warning Assumes that the host buffer already exists!
356 void deviceUpload(cudaStream_t stream, bool sync);
357 void deviceUpload(void* stream, bool sync) {this->deviceUpload(cudaStream_t(stream), sync);}
358
359 ///////////////////////////////////////////////////////////////////////
360
361 /// @brief Download data from a specified device to the host. If the host buffer des not exist it will first be allocated
362 /// @param device device ID to download source data from
363 /// @param stream cuda stream
364 /// @param sync if false the memory copy is asynchronous.
365 /// @warning Assumes that the specifed device buffer already exists!
366 void deviceDownload(int device = 0, cudaStream_t stream = 0, bool sync = true);
367 void deviceDownload(int device, void* stream , bool sync) {this->deviceDownload(device, cudaStream_t(stream), sync);}
368
369 /// @brief Download the buffer from the current device to the host, i.e. GPU -> CPU.
370 /// If the host buffer des not exist it will first be allocated
371 /// @param stream CUDA stream
372 /// @param sync if false the memory copy is asynchronous
373 /// @note If the host/CPU buffer does not exist it is first allocated
374 /// @warning Assumes that the device/GPU buffer already exists
375 void deviceDownload(void* stream, bool sync);
376
377 ///////////////////////////////////////////////////////////////////////
378
379 /// @brief Returns the size in bytes of the raw memory buffer managed by this allocator.
380 uint64_t size() const { return mSize; }
381 uint64_t capacity() const {return this->size();}
382
383 /// @brief Returns the number of buffers that are not NULL
384 int bufferCount() const {
385 int count = mCpuData ? 1 : 0;
386 for (int i=0; i<mDeviceCount; ++i) if (mGpuData[i]) ++count;
387 return count;
388 }
389
390 int deviceCount() const {return mDeviceCount;}
391
392 /// @{
393 /// @brief Returns true if this allocator is empty, i.e. has no allocated memory
394 bool empty() const { return mSize == 0; }
395 bool isEmpty() const { return this->empty(); }
396 /// @}
397
398 /// @brief De-allocate all memory managed by this allocator and set all pointers to NULL
399 /// @param stream Stream the device frees are issued on. The frees are additionally ordered
400 /// after every stream the buffer was used on (via the per-device tracking event), so
401 /// @a stream selects where the free is enqueued, not what it is ordered against - any
402 /// stream is safe to pass here regardless of where the buffer was used.
403 void clear(cudaStream_t stream = 0);
404 void clear(void* stream){this->clear(cudaStream_t(stream));}
405
406}; // DualDeviceBuffer class
407
408/// @brief The dual-space device buffer under its long-standing public name.
409/// @deprecated Grid storage is moving to the single-space cuda::Buffer:
410/// build or read into a host handle and move it with
411/// cuda::copyTo (see cuda/HandleStorage.h), or allocate the
412/// result of a GPU tool directly in a cuda::Buffer. Transfers
413/// adopt the source handle's already-validated metadata -- no
414/// kernel runs -- so copyTo is callable from host-only
415/// translation units directly (see the CUDA examples). The
416/// dual buffer and this name are removed together after a
417/// deprecation window.
418using DeviceBuffer [[deprecated("grid storage is moving to cuda::Buffer<std::byte>: build into a host handle and use cuda::copyTo (cuda/HandleStorage.h, host-callable); see the CUDA examples")]] = DualDeviceBuffer;
419
420// --------------------------> Implementations below <------------------------------------
421
423{
424 if (this == &other) return *this;// self-move would free our buffers and then read them back
425 if (mManaged) {// first free all the managed data buffers, ordered after every use of each
426 cudaCheck(cudaFreeHost(mCpuData));
427 this->freeDualDeviceBuffers(cudaStream_t{0});
428 }
429 delete [] mGpuData;
430 delete [] mEvents;
431 mSize = other.mSize;
432 mCpuData = other.mCpuData;
433 mGpuData = other.mGpuData;
434 mDeviceCount = other.mDeviceCount;
435 mManaged = other.mManaged;
436 mEvents = other.mEvents;
437 other.mCpuData = nullptr;
438 other.mGpuData = nullptr;
439 other.mEvents = nullptr;
440 other.mSize = 0;
441 other.mDeviceCount = 0;
442 other.mManaged = 0;
443 return *this;
444}
445
446inline void DualDeviceBuffer::init(uint64_t size, int device, cudaStream_t stream)
447{
448 if (size==0) return;
449 cudaCheck(cudaGetDeviceCount(&mDeviceCount));
450 mGpuData = new void*[mDeviceCount]();// NULL initialization
451 mEvents = new cudaEvent_t[mDeviceCount]();// NULL initialization; created lazily on first use
452 NANOVDB_ASSERT(device >= cudaCpuDeviceId && device < mDeviceCount);
453 if (device == cudaCpuDeviceId) {
454 cudaCheck(cudaMallocHost((void**)&mCpuData, size)); // un-managed pinned memory on the host (can be slow to access!). Always 32B aligned
455 checkPtr(mCpuData, "cuda::DualDeviceBuffer::init: failed to allocate host buffer");
456 } else {
457 cudaCheck(util::cuda::mallocAsync(mGpuData+device, size, stream)); // un-managed memory on the device, always 32B aligned!
458 checkPtr(mGpuData[device], "cuda::DualDeviceBuffer::init: failed to allocate device buffer");
459 this->recordUse(device, stream);// the free must be ordered after this allocation
460 }
461 mSize = size;
462 mManaged = 1;// i.e. this instance is responsible for allocating and delete memory
463} // DualDeviceBuffer::init
464
465inline void DualDeviceBuffer::deviceUpload(int device, cudaStream_t stream, bool sync)
466{
467 NANOVDB_ASSERT(device >= 0 && device < mDeviceCount);// should be device and not the host
468 checkPtr(mCpuData, "uninitialized cpu source data");
469 if (mGpuData[device] == nullptr) {
470 if (mManaged==0) throw std::runtime_error("DualDeviceBuffer::deviceUpload called on externally managed memory that wasn\'t allocated.");
471 cudaCheck(util::cuda::mallocAsync(mGpuData+device, mSize, stream)); // un-managed memory on the device, always 32B aligned!
472 }
473 checkPtr(mGpuData[device], "uninitialized gpu destination data");
474 // Order this transfer after any use of the buffer on another stream, then mark it as the
475 // latest use, so the tracking event keeps covering every stream the buffer has seen.
476 this->orderAfterPriorUses(device, stream);
477 cudaCheck(cudaMemcpyAsync(mGpuData[device], mCpuData, mSize, cudaMemcpyHostToDevice, stream));
478 this->recordUse(device, stream);
479 if (sync) cudaCheck(cudaStreamSynchronize(stream));
480} // DualDeviceBuffer::deviceUpload
481
482inline void DualDeviceBuffer::deviceUpload(cudaStream_t stream, bool sync)
483{
484 int device = 0;
485 cudaGetDevice(&device);
486 this->deviceUpload(device, stream, sync);
487} // DualDeviceBuffer::deviceUpload
488
489inline void DualDeviceBuffer::deviceDownload(int device, cudaStream_t stream, bool sync)
490{
491 NANOVDB_ASSERT(device >= 0 && device < mDeviceCount);
492 checkPtr(mGpuData[device], "uninitialized gpu source data");// no source data on the specified device
493 if (mCpuData == nullptr) {
494 if (mManaged==0) throw std::runtime_error("DualDeviceBuffer::deviceDownload called on uninitialized cpu destination memory that is externally managed.");
495 cudaCheck(cudaMallocHost((void**)&mCpuData, mSize)); // un-managed pinned memory on the host (can be slow to access!). Always 32B aligned
496 }
497 checkPtr(mCpuData, "uninitialized cpu destination data");
498 this->orderAfterPriorUses(device, stream);
499 cudaCheck(cudaMemcpyAsync(mCpuData, mGpuData[device], mSize, cudaMemcpyDeviceToHost, stream));
500 this->recordUse(device, stream);
501 if (sync) cudaCheck(cudaStreamSynchronize(stream));
502} // DualDeviceBuffer::deviceDownload
503
504inline void DualDeviceBuffer::deviceDownload(void* stream, bool sync)
505{
506 int device = 0;
507 cudaCheck(cudaGetDevice(&device));
508 this->deviceDownload(device, cudaStream_t(stream), sync);
509} // DualDeviceBuffer::deviceDownload
510
511inline void DualDeviceBuffer::clear(cudaStream_t stream)
512{
513 if (mManaged) {// free all the managed data buffers, ordered after every use of each
514 cudaCheck(cudaFreeHost(mCpuData));
515 this->freeDualDeviceBuffers(stream);
516 }
517 delete [] mGpuData;
518 delete [] mEvents;
519 mCpuData = nullptr;
520 mGpuData = nullptr;
521 mEvents = nullptr;
522 mSize = 0;
523 mDeviceCount = 0;
524 mManaged = 0;
525} // DualDeviceBuffer::clear
526
527}// namespace cuda
528
529using CudaDeviceBuffer [[deprecated("Use GridHandle<cuda::Buffer<std::byte>> with cuda::copyTo instead")]] = cuda::DualDeviceBuffer;
530
531template<>
533{
534 static constexpr bool hasDeviceDual = true;
535};
536
537}// namespace nanovdb
538
539#endif // end of NANOVDB_CUDA_DEVICEBUFFER_H_HAS_BEEN_INCLUDED
HostBuffer - a buffer that contains a shared or private bump pool to either externally or internally ...
#define checkPtr(ptr, msg)
Definition HostBuffer.h:92
This is a buffer that contains a shared or private pool to either externally or internally managed ho...
Definition HostBuffer.h:181
const void * data() const
Retuns a pointer to the raw memory buffer managed by this allocator.
Definition HostBuffer.h:257
Simple memory buffer using un-managed pinned host memory when compiled with NVCC. Obviously this clas...
Definition DeviceBuffer.h:47
void recordUse(int device, cudaStream_t stream)
Record that this buffer's device data was just used on stream, so that the buffer's device frees (des...
Definition DeviceBuffer.h:304
~DualDeviceBuffer()
Destructor frees memory on both the host and device.
Definition DeviceBuffer.h:187
uint64_t capacity() const
Definition DeviceBuffer.h:381
DualDeviceBuffer(uint64_t size, bool host, void *stream)
Constructor.
Definition DeviceBuffer.h:107
static PtrT createPtr(uint64_t size, void *cpuData, void *gpuData)
Factory methods that create a shared pointer to an DualDeviceBuffer instance.
Definition DeviceBuffer.h:228
DualDeviceBuffer(uint64_t size, void *cpuData, std::initializer_list< std::pair< int, void * > > list)
Constructor for externally managed host and multiple device buffers.
Definition DeviceBuffer.h:138
void deviceUpload(int device=0, cudaStream_t stream=0, bool sync=true)
Uploads buffer on the host to a specific device. If it doesn't exist it's created first.
Definition DeviceBuffer.h:465
T * data(ptrdiff_t count=0, int device=cudaCpuDeviceId) const
Returns an offset pointer of a specific type from the allocated host memory.
Definition DeviceBuffer.h:253
static DualDeviceBuffer create(const HostBuffer &buffer, int device=cudaCpuDeviceId, cudaStream_t stream=0)
Static factory method that returns an instance of this buffer constructed from a HostBuffer.
Definition DeviceBuffer.h:221
void clear(void *stream)
Definition DeviceBuffer.h:404
DualDeviceBuffer(uint64_t size, int device=cudaCpuDeviceId, cudaStream_t stream=0)
Constructor with a specified device and size.
Definition DeviceBuffer.h:98
void * data(ptrdiff_t byteOffset, int device=cudaCpuDeviceId) const
Returns a byte offset void pointer from the allocated host memory.
Definition DeviceBuffer.h:264
void deviceUpload(void *stream, bool sync)
Definition DeviceBuffer.h:357
void * deviceData() const
Retuns a raw pointer to the current device/GPU buffer managed by this allocator.
Definition DeviceBuffer.h:333
bool empty() const
Returns true if this allocator is empty, i.e. has no allocated memory.
Definition DeviceBuffer.h:394
int bufferCount() const
Returns the number of buffers that are not NULL.
Definition DeviceBuffer.h:384
void deviceUpload(int device, void *stream, bool sync)
Definition DeviceBuffer.h:348
uint64_t size() const
Returns the size in bytes of the raw memory buffer managed by this allocator.
Definition DeviceBuffer.h:380
std::shared_ptr< DualDeviceBuffer > PtrT
Definition DeviceBuffer.h:89
void clear(cudaStream_t stream=0)
De-allocate all memory managed by this allocator and set all pointers to NULL.
Definition DeviceBuffer.h:511
int deviceCount() const
Definition DeviceBuffer.h:390
DualDeviceBuffer(DualDeviceBuffer &&other) noexcept
Move copy-constructor.
Definition DeviceBuffer.h:157
DualDeviceBuffer()
Default constructor of an empty buffer.
Definition DeviceBuffer.h:92
void * deviceData(int device) const
Retuns a raw pointer to the specified device/GPU buffer managed by this allocator.
Definition DeviceBuffer.h:326
void * data() const
Retuns a raw void pointer to the host/CPU buffer managed by this allocator.
Definition DeviceBuffer.h:245
static DualDeviceBuffer create(uint64_t size, const DualDeviceBuffer *dummy=nullptr, int device=cudaCpuDeviceId, cudaStream_t stream=0)
Static factory method that returns an instance of this buffer.
Definition DeviceBuffer.h:202
DualDeviceBuffer(const HostBuffer &buffer, int device=cudaCpuDeviceId, cudaStream_t stream=0)
Copy-constructor from a HostBuffer.
Definition DeviceBuffer.h:174
static PtrT createPtr(uint64_t size, void *cpuData, std::initializer_list< std::pair< int, void * > > list)
Factory methods that create a shared pointer to an DualDeviceBuffer instance.
Definition DeviceBuffer.h:229
void orderAfterPriorUses(int device, cudaStream_t stream) const
Order work subsequently issued on stream after every prior use of this device buffer,...
Definition DeviceBuffer.h:280
bool isEmpty() const
Returns true if this allocator is empty, i.e. has no allocated memory.
Definition DeviceBuffer.h:395
static DualDeviceBuffer create(uint64_t size, void *cpuData, std::initializer_list< std::pair< int, void * > > list)
Static factory method that returns an instance of this buffer that wraps externally managed host and ...
Definition DeviceBuffer.h:215
void deviceDownload(int device, void *stream, bool sync)
Definition DeviceBuffer.h:367
static DualDeviceBuffer create(uint64_t size, void *cpuData, void *gpuData)
Static factory method that returns an instance of this buffer that wraps externally managed memory.
Definition DeviceBuffer.h:209
static PtrT createPtr(const HostBuffer &buffer, int device=cudaCpuDeviceId, cudaStream_t stream=0)
Factory methods that create a shared pointer to an DualDeviceBuffer instance.
Definition DeviceBuffer.h:230
DualDeviceBuffer(const DualDeviceBuffer &)=delete
Disallow copy-construction.
static PtrT createPtr(uint64_t size, const DualDeviceBuffer *=nullptr, int device=cudaCpuDeviceId, cudaStream_t stream=0)
Factory methods that create a shared pointer to an DualDeviceBuffer instance.
Definition DeviceBuffer.h:227
void deviceDownload(int device=0, cudaStream_t stream=0, bool sync=true)
Download data from a specified device to the host. If the host buffer des not exist it will first be ...
Definition DeviceBuffer.h:489
DualDeviceBuffer & operator=(const DualDeviceBuffer &)=delete
Disallow copy assignment operation.
static DualDeviceBuffer create(uint64_t size, const DualDeviceBuffer *dummy, bool host, void *stream)
Static factory method that return an instance of this buffer.
Definition DeviceBuffer.h:195
DualDeviceBuffer(uint64_t size, void *cpuData, void *gpuData)
Constructor for externally managed host and device buffers.
Definition DeviceBuffer.h:120
Definition GridHandle.h:37
cudaError_t freeAsync(void *d_ptr, cudaStream_t)
Wrapper forced to synchronous cudaFree; see the mode comment above. The trailing stream argument is a...
Definition Util.h:127
cudaError_t mallocAsync(void **d_ptr, size_t size, cudaStream_t)
Wrapper forced to synchronous cudaMalloc; see the mode comment above. The trailing stream argument is...
Definition Util.h:120
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 DeviceBuffer.h:534
Definition HostBuffer.h:101