OpenVDB 13.1.0
Loading...
Searching...
No Matches
Util.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 nanovdb/util/cuda/Util.h
6
7 \author Ken Museth
8
9 \date December 20, 2023
10
11 \brief Cuda specific utility functions
12*/
13
14#ifndef NANOVDB_UTIL_CUDA_UTIL_H_HAS_BEEN_INCLUDED
15#define NANOVDB_UTIL_CUDA_UTIL_H_HAS_BEEN_INCLUDED
16
17#include <cuda.h>
18#include <cuda_runtime_api.h>
19#include <vector>
20#include <nanovdb/util/Util.h> // for stderr and NANOVDB_ASSERT
21
22// change 1 -> 0 to only perform asserts during debug builds
23#if 1 || defined(DEBUG) || defined(_DEBUG)
24 static inline void gpuAssert(cudaError_t code, const char* file, int line, bool abort = true)
25 {
26 if (code != cudaSuccess) {
27 fprintf(stderr, "CUDA error %u: %s (%s:%d)\n", unsigned(code), cudaGetErrorString(code), file, line);
28 //fprintf(stderr, "CUDA Runtime Error: %s %s %d\n", cudaGetErrorString(code), file, line);
29 if (abort) exit(code);
30 }
31 }
32 static inline void ptrAssert(const void* ptr, const char* msg, const char* file, int line, bool abort = true)
33 {
34 if (ptr == nullptr) {
35 fprintf(stderr, "NULL pointer error: %s %s %d\n", msg, file, line);
36 if (abort) exit(1);
37 } else if (uint64_t(ptr) % 32) {
38 fprintf(stderr, "Pointer misalignment error: %s %s %d\n", msg, file, line);
39 if (abort) exit(1);
40 }
41 }
42#else
43 static inline void gpuAssert(cudaError_t, const char*, int, bool = true){}
44 static inline void ptrAssert(void*, const char*, const char*, int, bool = true){}
45#endif
46
47// Convenience function for checking CUDA runtime API results
48// can be wrapped around any runtime API call. No-op in release builds.
49#define cudaCheck(ans) \
50 { \
51 gpuAssert((ans), __FILE__, __LINE__); \
52 }
53
54#define checkPtr(ptr, msg) \
55 { \
56 ptrAssert((ptr), (msg), __FILE__, __LINE__); \
57 }
58
59#define cudaSync() \
60 { \
61 cudaCheck(cudaDeviceSynchronize()); \
62 }
63
64#define cudaCheckError() \
65 { \
66 cudaCheck(cudaGetLastError()); \
67 }
68
69namespace nanovdb {// =========================================================
70
71namespace util::cuda {// ======================================================
72
73//#define NANOVDB_USE_SYNC_CUDA_MALLOC
74// cudaMallocAsync and cudaFreeAsync were introduced in CUDA 11.2, and even on newer
75// toolkits a device may not expose stream-ordered memory pools at runtime — fractional
76// vGPU configurations commonly disable them, making cudaMallocAsync fail with
77// cudaErrorNotSupported. The wrappers below therefore behave in two modes: when
78// CUDA < 11.2 or NANOVDB_USE_SYNC_CUDA_MALLOC is defined they map to synchronous
79// cudaMalloc/cudaFree; otherwise they use cudaMallocAsync/cudaFreeAsync and, on a
80// device that lacks memory pools, fail with an actionable diagnostic rather than
81// silently substituting synchronous allocation (which would make an async resource
82// misrepresent its own semantics — the choice of a synchronous backend belongs to the
83// caller, not to a hidden runtime fallback). Callers select synchronous allocation by
84// defining NANOVDB_USE_SYNC_CUDA_MALLOC, which -- because the wrappers are inline
85// functions -- must be defined identically in every translation unit to avoid violating
86// the one-definition rule.
87
88/// @brief Returns true if @c device supports stream-ordered memory pools, i.e.
89/// cudaMallocAsync/cudaFreeAsync. Queried once per process for all
90/// devices and cached; out-of-range device ids return false.
91inline bool memoryPoolsSupported(int device)
92{
93#if (CUDART_VERSION < 11020)
94 (void)device;
95 return false;
96#else
97 static const auto supported = [] {
98 int count = 0;
99 if (cudaGetDeviceCount(&count) != cudaSuccess || count < 0) count = 0;
100 std::vector<char> s(static_cast<size_t>(count), 0);
101 for (int i = 0; i < count; ++i) {
102 int attr = 0;
103 if (cudaDeviceGetAttribute(&attr, cudaDevAttrMemoryPoolsSupported, i) == cudaSuccess)
104 s[static_cast<size_t>(i)] = char(attr != 0);
105 }
106 return s;
107 }();
108 return device >= 0 && static_cast<size_t>(device) < supported.size() && supported[static_cast<size_t>(device)] != 0;
109#endif
110}
111
112#if (CUDART_VERSION < 11020) || defined(NANOVDB_USE_SYNC_CUDA_MALLOC) // 11.2 introduced cudaMallocAsync and cudaFreeAsync
113
114/// @brief Wrapper forced to synchronous cudaMalloc; see the mode comment above.
115/// The trailing stream argument is accepted for signature compatibility
116/// and ignored.
117/// @param d_ptr Device pointer to allocated device memory
118/// @param size Number of bytes to allocate
119/// @return Cuda error code
120inline cudaError_t mallocAsync(void** d_ptr, size_t size, cudaStream_t){return cudaMalloc(d_ptr, size);}
121
122/// @brief Wrapper forced to synchronous cudaFree; see the mode comment above.
123/// The trailing stream argument is accepted for signature compatibility
124/// and ignored.
125/// @param d_ptr Device pointer that will be freed
126/// @return Cuda error code
127inline cudaError_t freeAsync(void* d_ptr, cudaStream_t){return cudaFree(d_ptr);}
128
129#else
130
131/// @brief Wrapper that calls cudaMallocAsync. On a device without stream-ordered
132/// memory pools it emits an actionable diagnostic and returns
133/// cudaErrorNotSupported rather than silently allocating synchronously.
134/// @param d_ptr Device pointer to allocated device memory
135/// @param size Number of bytes to allocate
136/// @param stream The stream establishing the stream ordering contract and the memory pool to allocate from
137/// @return Cuda error code
138inline cudaError_t mallocAsync(void** d_ptr, size_t size, cudaStream_t stream)
139{
140 int device = 0;
141 const cudaError_t err = cudaGetDevice(&device);
142 if (err != cudaSuccess) return err;
143 if (!memoryPoolsSupported(device)) {
144 fprintf(stderr,
145 "NanoVDB: device %d does not support stream-ordered CUDA memory pools required by "
146 "cudaMallocAsync. Define NANOVDB_USE_SYNC_CUDA_MALLOC when building to allocate "
147 "synchronously with cudaMalloc/cudaFree instead.\n",
148 device);
149 return cudaErrorNotSupported;
150 }
151 return cudaMallocAsync(d_ptr, size, stream);
152}
153
154/// @brief Wrapper that calls cudaFreeAsync. Mirrors mallocAsync's pool check so a
155/// device without memory pools reports cudaErrorNotSupported rather than
156/// calling cudaFreeAsync where it cannot succeed.
157/// @param d_ptr Device pointer that will be freed
158/// @param stream The stream establishing the stream ordering promise
159/// @return Cuda error code
160inline cudaError_t freeAsync(void* d_ptr, cudaStream_t stream)
161{
162 int device = 0;
163 const cudaError_t err = cudaGetDevice(&device);
164 if (err != cudaSuccess) return err;
165 if (!memoryPoolsSupported(device)) return cudaErrorNotSupported;
166 return cudaFreeAsync(d_ptr, stream);
167}
168
169#endif
170
171/// @brief Returns the device ID associated with the specified pointer
172/// @note If @c ptr points to host memory (only) the return ID is either cudaInvalidDeviceId = -2 or cudaCpuDeviceId = -1
173inline int ptrToDevice(void *ptr)
174{
175 cudaPointerAttributes ptrAtt;
176 cudaCheck(cudaPointerGetAttributes(&ptrAtt, ptr));
177 return ptrAtt.device;
178}
179
180/// @brief Returns the ID of the current device
181inline int currentDevice()
182{
183 int current = cudaInvalidDeviceId;
184 cudaCheck(cudaGetDevice(&current));
185 assert(current != cudaInvalidDeviceId);
186 return current;
187}
188
189/// @brief Returns the number of devices with compute capability greater or equal to 1.0 that are available for execution
190inline int deviceCount()
191{
192 int deviceCount = 0;
193 cudaCheck(cudaGetDeviceCount(&deviceCount));
194 return deviceCount;
195}
196
197/// @brief Print information about a specific device
198/// @param device device ID for which information will be printed
199/// @param preMsg optional message printed before the device information
200/// @param file Optional file stream to print to, e.g. stderr or stdout
201inline void printDevInfo(int device, const char *preMsg = nullptr, std::FILE* file = stderr)
202{
203 cudaDeviceProp prop;
204 cudaGetDeviceProperties(&prop, device);
205 if (preMsg) fprintf(file, "%s ", preMsg);
206 fprintf(file,"GPU #%d, named \"%s\", compute capability %d.%d, %zu GB of VRAM\n",
207 device, prop.name, prop.major, prop.minor, prop.totalGlobalMem >> 30);
208}
209
210/// @brief Simple (naive) implementation of a unique device pointer
211/// using stream ordered memory allocation and deallocation.
212/// @tparam T Type of the device pointer
213template <typename T>
215{
216 T *mPtr;// pointer to stream ordered memory allocation
217 cudaStream_t mStream;
218public:
219 unique_ptr(size_t count = 0, cudaStream_t stream = 0) : mPtr(nullptr), mStream(stream)
220 {
221 if (count>0) cudaCheck(mallocAsync((void**)&mPtr, count*sizeof(T), stream));
222 }
223 unique_ptr(const unique_ptr&) = delete;
224 unique_ptr(unique_ptr&& other) : mPtr(other.mPtr), mStream(other.mStream)
225 {
226 other.mPtr = nullptr;
227 }
229 {
230 if (mPtr) cudaCheck(freeAsync(mPtr, mStream));
231 }
232 unique_ptr& operator=(const unique_ptr&) = delete;
234 {
235 mPtr = rhs.mPtr;
236 mStream = rhs.mStream;
237 rhs.mPtr = nullptr;
238 return *this;
239 }
240 void reset() {
241 if (mPtr) {
242 cudaCheck(freeAsync(mPtr, mStream));
243 mPtr = nullptr;
244 }
245 }
246 T* get() const {return mPtr;}
247 explicit operator bool() const {return mPtr != nullptr;}
248};// util::cuda::unique_ptr
249
250/// @brief Computes the number of blocks per grid given the problem size and number of threads per block
251/// @param numItems Problem size
252/// @param threadsPerBlock Number of threads per block (second CUDA launch parameter)
253/// @return number of blocks per grid (first CUDA launch parameter)
254/// @note CUDA launch parameters: kernel<<< blocksPerGrid, threadsPerBlock, sharedMemSize, streamID>>>
255inline size_t blocksPerGrid(size_t numItems, size_t threadsPerBlock)
256{
257 NANOVDB_ASSERT(numItems > 0 && threadsPerBlock >= 32 && threadsPerBlock % 32 == 0);
258 return (numItems + threadsPerBlock - 1) / threadsPerBlock;
259}
260
261// CUDA 13.0 changes cudaMemPrefetchAsync and cudaMemPrefetch to use a cudaMemLocation as an argument as
262// opposed to an integer device id. This function provides compatibility by returning the corresponding
263// location in CUDA 13.0 and above while passing through the device in earlier versions.
264#if (CUDART_VERSION < 13000)
265/// @brief Compatbility wrapper for cudaMemAdvise/cudaMemAdvise
266inline cudaError_t memAdvise(const void* devPtr, size_t count, cudaMemoryAdvise advice, int device) {
267 return cudaMemAdvise(devPtr, count, advice, device);
268}
269
270/// @brief Compatbility wrapper for cudaMemPrefetchAsync/cudaMemPrefetchAsync
271inline cudaError_t memPrefetchAsync(const void* devPtr, size_t count, int dstDevice, cudaStream_t stream) {
272 return cudaMemPrefetchAsync(devPtr, count, dstDevice, stream);
273}
274#else
275/// @brief Helper function that converts a device id to a cudaMemLocation
276/// @param device Integer device id
277/// @return cudaMemLocation corresponding to the device id
278inline cudaMemLocation deviceToLocation(int device) {
279 if (device < cudaCpuDeviceId) {
280 return {cudaMemLocationTypeInvalid, device};
281 } else if (device == cudaCpuDeviceId) {
282 return {cudaMemLocationTypeHost, device};
283 } else {
284 return {cudaMemLocationTypeDevice, device};
285 }
286}
287
288/// @brief Compatbility wrapper for cudaMemAdvise/cudaMemAdvise
289inline cudaError_t memAdvise(const void* devPtr, size_t count, cudaMemoryAdvise advice, int device) {
290 return cudaMemAdvise(devPtr, count, advice, deviceToLocation(device));
291}
292
293/// @brief Compatbility wrapper for cudaMemPrefetchAsync/cudaMemPrefetchAsync
294inline cudaError_t memPrefetchAsync(const void* devPtr, size_t count, int dstDevice, cudaStream_t stream) {
295 return cudaMemPrefetchAsync(devPtr, count, deviceToLocation(dstDevice), 0u, stream);
296}
297#endif
298
299#if defined(__CUDACC__)// the following functions only run on the GPU!
300
301/// @brief Cuda kernel that launches device lambda functions
302/// @param numItems Problem size
303template<typename Func, typename... Args>
304__global__ void lambdaKernel(const size_t numItems, Func func, Args... args)
305{
306 const int tid = blockIdx.x * blockDim.x + threadIdx.x;
307 if (tid >= numItems) return;
308 func(tid, args...);
309}// util::cuda::lambdaKernel
310
311/// @brief Cuda kernel that launches device lambda functions with a tid offset
312/// @param numItems Problem size
313/// @param offset Offset for thread id
314template<typename Func, typename... Args>
315__global__ void offsetLambdaKernel(size_t numItems, unsigned int offset, Func func, Args... args)
316{
317 const unsigned int tid = blockIdx.x * blockDim.x + threadIdx.x;
318 if (tid >= numItems) return;
319 func(tid + offset, args...);
320}// util::cuda::offsetLambdaKernel
321
322/// @brief Cuda kernel that launches device operator functors with arbitrary arguments
323template<class Operator, typename... Args>
325__launch_bounds__(Operator::MaxThreadsPerBlock, Operator::MinBlocksPerMultiprocessor)
326void operatorKernel(
327 Args... args)
328{
329 Operator op;
330 op( args... );
331}
332
333/// @brief Cuda kernel that launches a pre-constructed device operator functor with arbitrary arguments.
334/// Unlike operatorKernel, the operator is passed by value (copied at launch) rather than
335/// default-constructed on the device, allowing functors with data members.
336template<class Operator, typename... Args>
338__launch_bounds__(Operator::MaxThreadsPerBlock, Operator::MinBlocksPerMultiprocessor)
339void operatorKernelInstance(Operator op, Args... args)
340{
341 op( args... );
342}
343
344/// @brief Cuda kernel that launches device operator functors with arbitrary arguments, using dynamic shared memory
345template<class Operator, typename... Args>
347__launch_bounds__(Operator::MaxThreadsPerBlock, Operator::MinBlocksPerMultiprocessor)
348void operatorKernelDynamic(Args... args)
349{
350 extern __shared__ char smem_buf[];
351 Operator op;
352 op( args..., smem_buf );
353}
354
355/// @brief Wrapper for launching a device operator that leverages dynamic shared memory, with a specified size
356/// @code
357/// struct MyFunctor
358/// {
359/// // These are passed to __launch_bounds__
360/// static constexpr int MaxThreadsPerBlock = <nThreads>
361/// static constexpr int MinBlocksPerMultiprocessor = 1;
362///
363/// struct SharedStorage {
364/// // Include whatever is needed in smem
365/// };
366///
367/// __device__
368/// void operator()(Args ... myArgs, char smem_buf[])
369/// { ... }
370/// };
371///
372/// dynamicSharedMemoryLauncher<MyFunctor>(nBlocks, sizeof(typename MyFunctor::SharedStorage), myArgs...);
373/// // smem_buff of size sizeof(MyFunctor::SharedStorage) will be automatically passed along
374/// @endcode
375template<class Operator, typename... Args>
376void dynamicSharedMemoryLauncher(const size_t numItems, const size_t smem_size, cudaStream_t stream, Args... args)
377{
378 cudaCheck(cudaFuncSetAttribute(operatorKernelDynamic<Operator, Args...>,
379 cudaFuncAttributeMaxDynamicSharedMemorySize,smem_size));
380 operatorKernelDynamic<Operator>
381 <<<numItems, Operator::MaxThreadsPerBlock, smem_size, stream>>>( args ... );
382}
383
384#endif// __CUDACC__
385
386}// namespace util::cuda ============================================================
387
388}// namespace nanovdb ===============================================================
389
390#if defined(__CUDACC__)// the following functions only run on the GPU!
391template<typename Func, typename... Args>
392[[deprecated("Use nanovdb::cuda::lambdaKernel instead")]]
393__global__ void cudaLambdaKernel(const size_t numItems, Func func, Args... args)
394{
395 const int tid = blockIdx.x * blockDim.x + threadIdx.x;
396 if (tid >= numItems) return;
397 func(tid, args...);
398}
399#endif// __CUDACC__
400
401#endif// NANOVDB_UTIL_CUDA_UTIL_H_HAS_BEEN_INCLUDED
unique_ptr(unique_ptr &&other)
Definition Util.h:224
~unique_ptr()
Definition Util.h:228
unique_ptr & operator=(const unique_ptr &)=delete
unique_ptr(size_t count=0, cudaStream_t stream=0)
Definition Util.h:219
unique_ptr(const unique_ptr &)=delete
unique_ptr & operator=(unique_ptr &&rhs) noexcept
Definition Util.h:233
void reset()
Definition Util.h:240
T * get() const
Definition Util.h:246
int deviceCount()
Returns the number of devices with compute capability greater or equal to 1.0 that are available for ...
Definition Util.h:190
int currentDevice()
Returns the ID of the current device.
Definition Util.h:181
void printDevInfo(int device, const char *preMsg=nullptr, std::FILE *file=stderr)
Print information about a specific device.
Definition Util.h:201
int ptrToDevice(void *ptr)
Returns the device ID associated with the specified pointer.
Definition Util.h:173
bool memoryPoolsSupported(int device)
Returns true if device supports stream-ordered memory pools, i.e. cudaMallocAsync/cudaFreeAsync....
Definition Util.h:91
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
size_t blocksPerGrid(size_t numItems, size_t threadsPerBlock)
Computes the number of blocks per grid given the problem size and number of threads per block.
Definition Util.h:255
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
Defines a simple memory pool used to call cub functions that use dynamic temporary storage.
Definition GridHandle.h:31
Utility functions.
#define NANOVDB_ASSERT(x)
Definition Util.h:53
#define __global__
Definition Util.h:79
static void ptrAssert(const void *ptr, const char *msg, const char *file, int line, bool abort=true)
Definition Util.h:32
static void gpuAssert(cudaError_t code, const char *file, int line, bool abort=true)
Definition Util.h:24
#define cudaCheck(ans)
Definition Util.h:49