OpenVDB 13.1.0
Loading...
Searching...
No Matches
DeviceResource.h
Go to the documentation of this file.
1// Copyright Contributors to the OpenVDB Project
2// SPDX-License-Identifier: Apache-2.0
3//
4#ifndef NANOVDB_CUDA_DEVICERESOURCE_H_HAS_BEEN_INCLUDED
5#define NANOVDB_CUDA_DEVICERESOURCE_H_HAS_BEEN_INCLUDED
6
7#include <cuda_runtime_api.h>
9
10#include <cstddef>
11#include <type_traits>
12#include <utility>
13
14namespace nanovdb {
15
16namespace cuda {
17
18/// @brief Default stream-ordered device memory resource. Allocations are made
19/// with cudaMallocAsync and freed with cudaFreeAsync via the
20/// util::cuda wrappers.
21/// @note Models the AsyncResource concept, which refines the synchronous
22/// Resource concept (as in CCCL's cuda::mr): the instance methods
23/// provide both the stream-ordered allocate_async / deallocate_async
24/// and the synchronous allocate / deallocate. The type is stateless,
25/// so a default-constructed instance adds no overhead. The static
26/// allocateAsync / deallocateAsync methods are deprecated.
28{
29public:
30 // cudaMalloc aligns memory to 256 bytes by default
31 static constexpr size_t DEFAULT_ALIGNMENT = 256;
32
33 /// @brief Stream-ordered allocation.
34 /// @param bytes number of bytes to allocate
35 /// @param stream cuda stream the allocation is ordered on
36 /// @note the alignment parameter is unnamed: cudaMallocAsync always
37 /// 256B-aligns
38 void* allocate_async(size_t bytes, size_t, cudaStream_t stream) {
39 void* p = nullptr;
40 cudaCheck(util::cuda::mallocAsync(&p, bytes, stream));
41 return p;
42 }
43
44 /// @brief Stream-ordered deallocation.
45 /// @param p pointer previously returned by allocate_async
46 /// @param stream cuda stream the deallocation is ordered on
47 void deallocate_async(void* p, size_t, size_t, cudaStream_t stream) {
49 }
50
51 /// @brief Synchronous allocation; the returned memory is immediately
52 /// valid on every stream.
53 /// @param bytes number of bytes to allocate
54 /// @param alignment requested alignment
55 void* allocate(size_t bytes, size_t alignment) {
56 void* p = this->allocate_async(bytes, alignment, cudaStream_t(0));
57 cudaCheck(cudaStreamSynchronize(cudaStream_t(0)));
58 return p;
59 }
60
61 /// @brief Synchronous deallocation; the caller guarantees that device
62 /// work touching the memory has completed.
63 /// @param p pointer previously returned by allocate or allocate_async
64 /// @param bytes size of the allocation in bytes
65 /// @param alignment alignment of the allocation in bytes
66 void deallocate(void* p, size_t bytes, size_t alignment) {
67 this->deallocate_async(p, bytes, alignment, cudaStream_t(0));
68 }
69
70 [[deprecated("use the instance method allocate_async")]]
71 static void* allocateAsync(size_t bytes, size_t alignment, cudaStream_t stream) {
72 return DeviceResource().allocate_async(bytes, alignment, stream);
73 }
74
75 [[deprecated("use the instance method deallocate_async")]]
76 static void deallocateAsync(void *p, size_t bytes, size_t alignment, cudaStream_t stream) {
77 DeviceResource().deallocate_async(p, bytes, alignment, stream);
78 }
79};
80
81/// @brief Returns a program-lifetime, address-stable reference to a default
82/// instance of resource @c R.
83/// @details The instance is a function-local static, so it outlives every
84/// caller and is safe to bind through a default function/constructor
85/// argument. @c R must be default-constructible.
86template <class R>
88{
89 static R sResource;
90 return sResource;
91}
92
93/// @brief Detection trait: @c is_async_resource<R>::value is true iff @c R
94/// models the stream-ordered AsyncResource concept, i.e. exposes
95/// allocate_async(size_t, size_t, cudaStream_t) and
96/// deallocate_async(void*, size_t, size_t, cudaStream_t).
97/// @details Use it to dispatch between a stream-ordered resource and a
98/// synchronous one (which exposes allocate/deallocate without a
99/// stream argument):
100/// @code
101/// template<typename R>
102/// void* allocate(R& resource, size_t bytes, size_t alignment, cudaStream_t stream)
103/// {
104/// if constexpr (nanovdb::cuda::is_async_resource<R>::value)
105/// return resource.allocate_async(bytes, alignment, stream); // stream-ordered
106/// else
107/// return resource.allocate(bytes, alignment); // synchronous
108/// }
109/// @endcode
110/// @note AsyncResource refines the synchronous Resource concept, matching
111/// CCCL's cuda::mr: an async resource must also provide the
112/// synchronous allocate / deallocate, so is_async_resource<R> implies
113/// is_resource<R>. A synchronous-only resource (e.g. PinnedResource)
114/// models just is_resource. The synchronous methods of a stream-ordered
115/// resource are typically thin delegates (allocate_async on the null
116/// stream followed by a stream synchronize).
117template <class R, class = void>
118struct is_async_resource : std::false_type {};
119
120template <class R>
121struct is_async_resource<R, std::void_t<
122 decltype(std::declval<R&>().allocate_async(size_t{0}, size_t{0}, cudaStream_t{0})),
123 decltype(std::declval<R&>().deallocate_async(std::declval<void*>(), size_t{0}, size_t{0}, cudaStream_t{0})),
124 decltype(std::declval<R&>().allocate(size_t{0}, size_t{0})),
125 decltype(std::declval<R&>().deallocate(std::declval<void*>(), size_t{0}, size_t{0}))>>
126 : std::true_type {};
127
128/// @brief Detection trait: @c is_host_accessible_resource<R>::value is true
129/// iff @c R declares `static constexpr bool HOST_ACCESSIBLE = true`,
130/// i.e. its allocations are mapped into the host address space (e.g.
131/// PinnedResource). Defaults to false: allocations are device-resident.
132/// @note Unlike the void_t detections above, this checks the member's VALUE:
133/// a resource declaring HOST_ACCESSIBLE = false stays device-resident.
134template<typename R, typename = void>
135struct is_host_accessible_resource : std::false_type {};
136template<typename R>
137struct is_host_accessible_resource<R, typename std::enable_if<bool(R::HOST_ACCESSIBLE)>::type> : std::true_type {};
138
139/// @brief Companion detection: @c is_device_accessible_resource<R>::value is
140/// true iff @c R declares `static constexpr bool DEVICE_ACCESSIBLE =
141/// true`, i.e. its allocations are also valid device addresses even
142/// though they are host-accessible (e.g. ManagedResource). A handle
143/// over such a resource exposes both accessor families. Purely
144/// device-resident resources do not need the marker: not being
145/// host-accessible already implies device residency.
146template<typename R, typename = void>
147struct is_device_accessible_resource : std::false_type {};
148template<typename R>
149struct is_device_accessible_resource<R, typename std::enable_if<bool(R::DEVICE_ACCESSIBLE)>::type> : std::true_type {};
150
151/// @brief Detection trait: @c is_resource<R>::value is true iff @c R models
152/// the synchronous Resource concept, i.e. exposes
153/// allocate(size_t, size_t) and deallocate(void*, size_t, size_t).
154/// @details Use it to constrain code paths that need a resource without a
155/// stream argument, e.g. host-side allocations:
156/// @code
157/// template<typename R>
158/// void* allocate(R& resource, size_t bytes, size_t alignment)
159/// {
160/// static_assert(nanovdb::cuda::is_resource<R>::value, "R must be a synchronous resource");
161/// return resource.allocate(bytes, alignment);
162/// }
163/// @endcode
164template <class R, class = void>
165struct is_resource : std::false_type {};
166
167template <class R>
168struct is_resource<R, std::void_t<
169 decltype(std::declval<R&>().allocate(size_t{0}, size_t{0})),
170 decltype(std::declval<R&>().deallocate(std::declval<void*>(), size_t{0}, size_t{0}))>>
171 : std::true_type {};
172
173/// @brief CRTP base supplying the synchronous half of the resource concept in
174/// terms of the stream-ordered half, so a custom stream-ordered resource
175/// only has to write allocate_async and deallocate_async.
176/// @tparam Derived the resource deriving from this base
177/// @details A stream-ordered resource must also model the synchronous concept
178/// (is_async_resource implies is_resource), which means writing four
179/// methods where two would do. The synchronous pair is not a bare
180/// delegate: memory from allocate must be usable immediately on any
181/// stream, so the null-stream allocation has to be synchronized before
182/// it is returned. Omitting that synchronization yields memory that
183/// satisfies the concept but is not actually synchronous -- a race
184/// rather than a compile error -- so it lives here rather than being
185/// rewritten per resource.
186/// @code
187/// struct MyResource : nanovdb::cuda::SyncFromAsync<MyResource> {
188/// static constexpr size_t DEFAULT_ALIGNMENT = 256;
189/// void* allocate_async(size_t bytes, size_t alignment, cudaStream_t stream);
190/// void deallocate_async(void* p, size_t bytes, size_t alignment, cudaStream_t stream);
191/// };
192/// @endcode
193template <class Derived>
195{
196 /// @brief Allocates @c bytes usable on any stream when this returns.
197 /// @param bytes number of bytes to allocate
198 /// @param alignment requested alignment
199 /// @note Every call synchronizes the null stream; on hot paths prefer the
200 /// stream-ordered pair.
201 void* allocate(size_t bytes, size_t alignment)
202 {
203 void* p = static_cast<Derived&>(*this).allocate_async(bytes, alignment, cudaStream_t{0});
204 cudaCheck(cudaStreamSynchronize(cudaStream_t{0}));
205 return p;
206 }
207
208 /// @brief Frees @c p on the null stream.
209 /// @param p pointer previously returned by allocate
210 /// @param bytes size passed to the matching allocate
211 /// @param alignment alignment passed to the matching allocate
212 /// @note No synchronization here: the synchronous concept's contract is
213 /// that the memory is already quiescent when deallocate is called.
214 void deallocate(void* p, size_t bytes, size_t alignment)
215 {
216 static_cast<Derived&>(*this).deallocate_async(p, bytes, alignment, cudaStream_t{0});
217 }
218};
219
220/// @brief Synchronous device memory resource backed by cudaMalloc/cudaFree.
221/// Models only the Resource concept: it never touches stream-ordered
222/// allocation, so it works on devices without memory-pool support
223/// (cudaDevAttrMemoryPoolsSupported == 0), where DeviceResource's
224/// cudaMallocAsync path fails by design. Pair with AsyncFromSync to
225/// drive the stream-ordered builders on such a device.
227{
228public:
229 // cudaMalloc aligns memory to 256 bytes by default
230 static constexpr size_t DEFAULT_ALIGNMENT = 256;
231
232 /// @brief Allocates @c bytes with cudaMalloc; valid on every stream when
233 /// this returns. A zero request returns nullptr.
234 void* allocate(size_t bytes, size_t)
235 {
236 if (bytes == 0) return nullptr;
237 void* p = nullptr;
238 cudaCheck(cudaMalloc(&p, bytes));
239 return p;
240 }
241
242 /// @brief Frees @c p with cudaFree; the caller guarantees that device work
243 /// touching the memory has completed.
244 void deallocate(void* p, size_t, size_t) { cudaCheck(cudaFree(p)); }
245};// MallocResource
246
247/// @brief Wrapper presenting a synchronous resource as a stream-ordered one,
248/// so it can drive components that require the AsyncResource concept
249/// (TempPool and the GPU builders).
250/// @tparam R the wrapped synchronous resource, held by value; wrap a
251/// ResourceRef<R> to borrow a stateful instance instead.
252/// @details The mirror of SyncFromAsync, and the analog of cuda::mr's
253/// synchronous_resource_adapter. allocate_async forwards to
254/// R::allocate, whose memory is immediately valid on every stream --
255/// a stronger guarantee than stream-ordering requires.
256/// deallocate_async synchronizes @c stream before R::deallocate,
257/// establishing the quiescence the synchronous contract demands.
258/// @warning Every deallocation synchronizes its stream, so expect
259/// serialization relative to a genuinely stream-ordered resource.
260/// That is the unavoidable cost of a synchronous backend under a
261/// stream-ordered algorithm; this wrapper exists so the cost is
262/// explicit and chosen by the caller -- e.g. on a device without
263/// memory-pool support -- rather than silently substituted.
264template<class R>
266{
267 static_assert(is_resource<R>::value,
268 "AsyncFromSync requires R to model the synchronous Resource concept");
269
270 static constexpr size_t DEFAULT_ALIGNMENT = R::DEFAULT_ALIGNMENT;
271
272 /// @brief The adapter is host-accessible iff the adapted resource is.
275
277
278 /// @brief Allocates through the synchronous resource; the result is valid
279 /// on every stream, hence trivially valid on @c stream.
280 void* allocate_async(size_t bytes, size_t alignment, cudaStream_t) { return resource.allocate(bytes, alignment); }
281
282 /// @brief Synchronizes @c stream, then frees through the synchronous
283 /// resource -- the synchronize makes the quiescence contract hold.
284 /// Null is a no-op and skips the synchronize.
285 void deallocate_async(void* p, size_t bytes, size_t alignment, cudaStream_t stream)
286 {
287 if (p == nullptr) return;
288 cudaCheck(cudaStreamSynchronize(stream));
289 resource.deallocate(p, bytes, alignment);
290 }
291
292 /// @brief Synchronous pair, forwarding to the wrapped resource.
293 void* allocate(size_t bytes, size_t alignment) { return resource.allocate(bytes, alignment); }
294 void deallocate(void* p, size_t bytes, size_t alignment) { resource.deallocate(p, bytes, alignment); }
295};// AsyncFromSync<R>
296
297/// @brief Non-owning reference to a memory resource that is itself a resource:
298/// copying the ref shares the underlying resource rather than copying it.
299/// @tparam R the referenced resource type
300/// @details Types that hold their resource by value -- cuda::Buffer, matching
301/// cuda::buffer -- select their ownership semantics by what is placed
302/// in that slot: a concrete resource is owned as a copy, while a
303/// ResourceRef borrows. This is the same division cuda::mr draws
304/// between any_resource (owning) and resource_ref (borrowing), and the
305/// same shape as std::pmr::polymorphic_allocator over memory_resource*.
306/// Use it when a resource is stateful or long-lived and a container
307/// must allocate through *that* instance rather than a copy of it.
308/// @warning The referenced resource must outlive every use of this ref and of
309/// all copies of it, including any container holding one.
310template <class R>
312{
314 "ResourceRef requires R to model the AsyncResource or the Resource concept");
315
316 static constexpr size_t DEFAULT_ALIGNMENT = R::DEFAULT_ALIGNMENT;
317
318 /// @brief A reference is host-accessible iff the referenced resource is.
321
322 /// @brief Constructs a ref borrowing @c resource.
323 /// @param resource resource to allocate from; must outlive this ref
324 ResourceRef(R& resource) : mResource(&resource) {}
325
326 /// @{
327 /// @brief Stream-ordered pair, present only when @c R models AsyncResource,
328 /// so a ref over a synchronous resource does not misreport its tier.
329 template<class S = R, std::enable_if_t<is_async_resource<S>::value, int> = 0>
330 void* allocate_async(size_t bytes, size_t alignment, cudaStream_t stream)
331 {
332 return mResource->allocate_async(bytes, alignment, stream);
333 }
334 template<class S = R, std::enable_if_t<is_async_resource<S>::value, int> = 0>
335 void deallocate_async(void* p, size_t bytes, size_t alignment, cudaStream_t stream)
336 {
337 mResource->deallocate_async(p, bytes, alignment, stream);
338 }
339 /// @}
340
341 /// @brief Synchronous pair, forwarding to the referenced resource.
342 void* allocate(size_t bytes, size_t alignment) { return mResource->allocate(bytes, alignment); }
343 void deallocate(void* p, size_t bytes, size_t alignment) { mResource->deallocate(p, bytes, alignment); }
344
345 /// @brief Two refs compare equal iff they reference the same resource, i.e.
346 /// memory allocated through one may be deallocated through the other.
347 friend bool operator==(ResourceRef lhs, ResourceRef rhs) { return lhs.mResource == rhs.mResource; }
348 friend bool operator!=(ResourceRef lhs, ResourceRef rhs) { return lhs.mResource != rhs.mResource; }
349
350private:
351 R* mResource;
352};// ResourceRef<R>
353
354}
355
356} // namespace nanovdb::cuda
357
358#endif // end of NANOVDB_CUDA_DEVICERESOURCE_H_HAS_BEEN_INCLUDED
Default stream-ordered device memory resource. Allocations are made with cudaMallocAsync and freed wi...
Definition DeviceResource.h:28
void deallocate(void *p, size_t bytes, size_t alignment)
Synchronous deallocation; the caller guarantees that device work touching the memory has completed.
Definition DeviceResource.h:66
void * allocate(size_t bytes, size_t alignment)
Synchronous allocation; the returned memory is immediately valid on every stream.
Definition DeviceResource.h:55
void * allocate_async(size_t bytes, size_t, cudaStream_t stream)
Stream-ordered allocation.
Definition DeviceResource.h:38
void deallocate_async(void *p, size_t, size_t, cudaStream_t stream)
Stream-ordered deallocation.
Definition DeviceResource.h:47
static constexpr size_t DEFAULT_ALIGNMENT
Definition DeviceResource.h:31
static void deallocateAsync(void *p, size_t bytes, size_t alignment, cudaStream_t stream)
Definition DeviceResource.h:76
static void * allocateAsync(size_t bytes, size_t alignment, cudaStream_t stream)
Definition DeviceResource.h:71
Synchronous device memory resource backed by cudaMalloc/cudaFree. Models only the Resource concept: i...
Definition DeviceResource.h:227
void deallocate(void *p, size_t, size_t)
Frees p with cudaFree; the caller guarantees that device work touching the memory has completed.
Definition DeviceResource.h:244
static constexpr size_t DEFAULT_ALIGNMENT
Definition DeviceResource.h:230
void * allocate(size_t bytes, size_t)
Allocates bytes with cudaMalloc; valid on every stream when this returns. A zero request returns null...
Definition DeviceResource.h:234
Definition GridHandle.h:37
R & default_resource()
Returns a program-lifetime, address-stable reference to a default instance of resource R.
Definition DeviceResource.h:87
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
Definition Coord.h:590
Cuda specific utility functions.
#define cudaCheck(ans)
Definition Util.h:49
Wrapper presenting a synchronous resource as a stream-ordered one, so it can drive components that re...
Definition DeviceResource.h:266
static constexpr bool HOST_ACCESSIBLE
The adapter is host-accessible iff the adapted resource is.
Definition DeviceResource.h:273
void deallocate(void *p, size_t bytes, size_t alignment)
Definition DeviceResource.h:294
void * allocate(size_t bytes, size_t alignment)
Synchronous pair, forwarding to the wrapped resource.
Definition DeviceResource.h:293
static constexpr bool DEVICE_ACCESSIBLE
Definition DeviceResource.h:274
static constexpr size_t DEFAULT_ALIGNMENT
Definition DeviceResource.h:270
void * allocate_async(size_t bytes, size_t alignment, cudaStream_t)
Allocates through the synchronous resource; the result is valid on every stream, hence trivially vali...
Definition DeviceResource.h:280
void deallocate_async(void *p, size_t bytes, size_t alignment, cudaStream_t stream)
Synchronizes stream, then frees through the synchronous resource – the synchronize makes the quiescen...
Definition DeviceResource.h:285
R resource
Definition DeviceResource.h:276
static constexpr bool HOST_ACCESSIBLE
A reference is host-accessible iff the referenced resource is.
Definition DeviceResource.h:319
void deallocate(void *p, size_t bytes, size_t alignment)
Definition DeviceResource.h:343
void * allocate(size_t bytes, size_t alignment)
Synchronous pair, forwarding to the referenced resource.
Definition DeviceResource.h:342
static constexpr bool DEVICE_ACCESSIBLE
Definition DeviceResource.h:320
friend bool operator!=(ResourceRef lhs, ResourceRef rhs)
Definition DeviceResource.h:348
ResourceRef(R &resource)
Constructs a ref borrowing resource.
Definition DeviceResource.h:324
void deallocate_async(void *p, size_t bytes, size_t alignment, cudaStream_t stream)
Stream-ordered pair, present only when R models AsyncResource, so a ref over a synchronous resource d...
Definition DeviceResource.h:335
static constexpr size_t DEFAULT_ALIGNMENT
Definition DeviceResource.h:316
void * allocate_async(size_t bytes, size_t alignment, cudaStream_t stream)
Stream-ordered pair, present only when R models AsyncResource, so a ref over a synchronous resource d...
Definition DeviceResource.h:330
friend bool operator==(ResourceRef lhs, ResourceRef rhs)
Two refs compare equal iff they reference the same resource, i.e. memory allocated through one may be...
Definition DeviceResource.h:347
CRTP base supplying the synchronous half of the resource concept in terms of the stream-ordered half,...
Definition DeviceResource.h:195
void deallocate(void *p, size_t bytes, size_t alignment)
Frees p on the null stream.
Definition DeviceResource.h:214
void * allocate(size_t bytes, size_t alignment)
Allocates bytes usable on any stream when this returns.
Definition DeviceResource.h:201
Detection trait: is_async_resource<R>::value is true iff R models the stream-ordered AsyncResource co...
Definition DeviceResource.h:118
Companion detection: is_device_accessible_resource<R>::value is true iff R declares static constexpr ...
Definition DeviceResource.h:147
Detection trait: is_host_accessible_resource<R>::value is true iff R declares static constexpr bool H...
Definition DeviceResource.h:135
Detection trait: is_resource<R>::value is true iff R models the synchronous Resource concept,...
Definition DeviceResource.h:165