alpaka
Abstraction Library for Parallel Kernel Acceleration
Loading...
Searching...
No Matches
Queue.hpp
Go to the documentation of this file.
1/* Copyright 2024 René Widera
2 * SPDX-License-Identifier: MPL-2.0
3 */
4#pragma once
5
6#include "alpaka/Vec.hpp"
17#include "alpaka/api/util.hpp"
22#include "alpaka/onAcc/Acc.hpp"
29
30#if ALPAKA_LANG_CUDA || ALPAKA_LANG_HIP
31
33
34# include <cstdint>
35# include <sstream>
36
37namespace alpaka::onHost
38{
39 namespace unifiedCudaHip
40 {
41 struct CallKernel;
42
43 template<typename T_Device>
44 struct Queue : std::enable_shared_from_this<Queue<T_Device>>
45 {
46 using ApiInterface = typename T_Device::ApiInterface;
47
48 public:
49 Queue(internal::concepts::DeviceHandle auto device, uint32_t const idx, bool isBlocking)
50 : m_device(std::move(device))
51 , m_idx(idx)
52 , m_isBlocking(isBlocking)
53 {
54 ALPAKA_LOG_FUNCTION(onHost::logger::queue);
55 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
56 ApiInterface,
57 ApiInterface::setDevice(onHost::getNativeHandle(m_device)));
58 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
59 ApiInterface,
60 ApiInterface::streamCreateWithFlags(&m_UniformCudaHipQueue, ApiInterface::streamNonBlocking));
61 }
62
63 ~Queue()
64 {
65 ALPAKA_LOG_FUNCTION(onHost::logger::queue);
66 onHost::internal::wait(*this);
67 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK_NOEXCEPT(
68 ApiInterface,
69 ApiInterface::streamDestroy(getNativeHandle()));
70 }
71
72 Queue(Queue const&) = delete;
73 Queue& operator=(Queue const&) = delete;
74
75 Queue(Queue&&) = delete;
76 Queue& operator=(Queue&&) = delete;
77
78 bool operator==(Queue const& other) const
79 {
80 return m_idx == other.m_idx && m_device == other.m_device;
81 }
82
83 bool operator!=(Queue const& other) const
84 {
85 return !(*this == other);
86 }
87
88 private:
89 void _()
90 {
91 static_assert(internal::concepts::Queue<Queue>);
92 }
93
94 Handle<T_Device> m_device;
95 uint32_t m_idx = 0u;
96 typename ApiInterface::Stream_t m_UniformCudaHipQueue;
97 core::CallbackThread m_callBackThread;
98 bool m_isBlocking{false};
99
100 /** Waits until all operations are finished depending on whether the queue is blocking or non-blocking.
101 *
102 * If the queue is a blocking queue the control flow will be blocked and the method is not returning until
103 * all work in the queue is processed. This method should be called after the task is enqueued into the
104 * native CUDA/HIP queue. There is no need to call this method before enqueuing because the queues are
105 * in-order queues and even if another thread is enqueued something before the order is guaranteed.
106 */
107 void conditionalWait() const noexcept
108 {
109 if(m_isBlocking)
110 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(ApiInterface, ApiInterface::streamSynchronize(getNativeHandle()));
111 }
112
113 friend struct alpaka::internal::GetName;
114
115 std::string getName() const
116 {
117 return std::string("unifiedCudaHip::Queue id=") + std::to_string(m_idx);
118 }
119
120 friend struct onHost::internal::GetNativeHandle;
121
122 [[nodiscard]] auto getNativeHandle() const noexcept
123 {
124 return m_UniformCudaHipQueue;
125 }
126
127 friend struct onHost::internal::Enqueue;
128 friend struct onHost::internal::Wait;
129
130 void wait() const
131 {
132 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(ApiInterface, ApiInterface::streamSynchronize(getNativeHandle()));
133 }
134
135 friend struct alpaka::internal::GetDeviceType;
136
137 auto getDeviceKind() const
138 {
139 return alpaka::internal::getDeviceKind(*m_device.get());
140 }
141
142 auto getDevice() const
143 {
144 return m_device;
145 }
146
147 std::shared_ptr<Queue> getSharedPtr()
148 {
149 return this->shared_from_this();
150 }
151
152 friend struct alpaka::onHost::internal::WaitFor;
153
154 void waitFor(unifiedCudaHip::Event<T_Device>& event)
155 {
156 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
157 ApiInterface,
158 ApiInterface::streamWaitEvent(getNativeHandle(), internal::getNativeHandle(event), 0));
159
160 conditionalWait();
161 }
162
163 friend struct internal::IsQueueEmpty;
164
165 bool isQueueEmpty() const
166 {
167 ALPAKA_LOG_FUNCTION(onHost::logger::queue);
168
169 typename ApiInterface::Error_t ret = ApiInterface::success;
170 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK_IGNORE(
171 ApiInterface,
172 ret = ApiInterface::streamQuery(getNativeHandle()),
173 ApiInterface::errorNotReady);
174 return (ret == ApiInterface::success);
175 }
176
177 void enqueueNativeFn(auto const& fn)
178 {
179 ALPAKA_LOG_FUNCTION(onHost::logger::queue);
180 fn(getNativeHandle());
181 conditionalWait();
182 }
183
184 friend struct onHost::internal::GetDevice;
185
186 friend struct alpaka::internal::GetApi;
187 friend struct onHost::internal::Memcpy;
188 friend struct onHost::internal::MemcpyDeviceGlobal;
189 friend struct onHost::internal::Memset;
190 friend struct onHost::internal::AllocDeferred;
191 friend struct CallKernel;
192 };
193
194 template<
195 alpaka::concepts::Api T_Api,
196 alpaka::concepts::DeviceKind T_DeviceKind,
197 alpaka::concepts::Executor T_Executor,
198 bool launchedWidthFrameSpec,
199 typename TKernelBundle,
200 typename T_OptimizedThreadSpec>
201 __global__ void gpuKernel(TKernelBundle const kernelBundle, T_OptimizedThreadSpec const optimizedThreadSpec)
202 {
203 constexpr auto warpSizeValue = alpaka::onAcc::unifiedCudaHip::internal::WarpSize::Get<T_DeviceKind>{}();
204 auto acc = onAcc::Acc{
205 Dict{
206 DictEntry(layer::block, onAcc::unifiedCudaHip::BlockLayer{optimizedThreadSpec}),
207 DictEntry(layer::thread, onAcc::unifiedCudaHip::ThreadLayer{optimizedThreadSpec}),
208 DictEntry(object::launchedWidthFrameSpec, std::bool_constant<launchedWidthFrameSpec>{}),
209 DictEntry(action::threadBlockSync, onAcc::unifiedCudaHip::Sync{}),
210 DictEntry(object::api, T_Api{}),
211 DictEntry(object::deviceKind, T_DeviceKind{}),
212 DictEntry(object::exec, T_Executor{}),
213 DictEntry(object::warpSize, warpSizeValue)},
214 };
215 kernelBundle(acc);
216 }
217
218 ALPAKA_FN_HOST auto convertVecToUniformCudaHipDim(alpaka::concepts::Vector auto const& vec) -> dim3
219 {
220 constexpr auto vecDim = ALPAKA_TYPEOF(vec)::dim();
221 dim3 dim(1, 1, 1);
222 if constexpr(vecDim >= 1u)
223 dim.x = static_cast<unsigned>(vec[vecDim - 1u]);
224 if constexpr(vecDim >= 2u)
225 dim.y = static_cast<unsigned>(vec[vecDim - 2u]);
226 if constexpr(vecDim >= 3u)
227 dim.z = static_cast<unsigned>(vec[vecDim - 3u]);
228
229 return dim;
230 }
231
232 struct CallKernel
233 {
234 template<alpaka::concepts::Vector T_NumBlocks, alpaka::concepts::Vector T_NumThreads>
235 struct OptimizedThreadSpec
236 {
237 using NumBlocksVecType = typename T_NumBlocks::UniVec;
238 using NumThreadsVecType = T_NumThreads;
239
240 static consteval uint32_t dim()
241 {
242 return T_NumThreads::dim();
243 }
244
245 constexpr OptimizedThreadSpec(T_NumBlocks const&, T_NumThreads const&)
246 {
247 }
248 };
249
250 template<
251 bool launchedWidthFrameSpec,
252 typename T_Device,
253 alpaka::concepts::Vector T_NumBlocks,
254 alpaka::concepts::Vector T_NumThreads,
255 alpaka::concepts::Executor T_Executor,
256 typename T_KernelBundle>
257 void operator()(
258 unifiedCudaHip::Queue<T_Device>& queue,
259 ThreadSpec<T_NumBlocks, T_NumThreads, T_Executor> const& threadSpec,
260 T_KernelBundle const& kernelBundle) const
261 {
262 static_assert(
263 ALPAKA_TYPEOF(threadSpec)::getExecutor() != exec::anyExecutor,
264 "'exec::anyExecutor' can not be used to enqueue an kernel.");
265 ALPAKA_LOG_FUNCTION(onHost::logger::kernel + onHost::logger::queue);
266
267 using ApiInterface = typename unifiedCudaHip::Queue<T_Device>::ApiInterface;
268 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
269 ApiInterface,
270 ApiInterface::setDevice(onHost::getNativeHandle(queue.m_device)));
271
272 constexpr uint32_t dim = T_NumBlocks::dim();
273 // dimension of the cuda/hip layer
274 constexpr uint32_t layerDim = dim >= 4u ? 1u : dim;
275 using IdxType = typename T_NumBlocks::type;
276
277 Vec<IdxType, layerDim> numBlocks;
278 Vec<IdxType, layerDim> numThreadsPerBlock;
279
280 if constexpr(dim >= 4u)
281 {
282 numBlocks = threadSpec.getNumBlocks().product();
283 numThreadsPerBlock = threadSpec.getNumThreads().product();
284 }
285 else
286 {
287 numBlocks = threadSpec.getNumBlocks();
288 numThreadsPerBlock = threadSpec.getNumThreads();
289 }
290
291 using ThreadSpecType = std::conditional_t<
292 dim >= 4u,
293 ALPAKA_TYPEOF(threadSpec),
294 OptimizedThreadSpec<
295 typename ALPAKA_TYPEOF(threadSpec)::NumBlocksVecType,
296 typename ALPAKA_TYPEOF(threadSpec)::NumThreadsVecType>>;
297 // thread spec which is only holding data if the dimension is larger than 3u
298 auto optimizedThreadSpec = ThreadSpecType(threadSpec.getNumBlocks(), threadSpec.getNumThreads());
299
300 auto kernelName = gpuKernel<
301 ALPAKA_TYPEOF(getApi(queue)),
303 T_Executor,
304 launchedWidthFrameSpec,
305 T_KernelBundle,
306 ALPAKA_TYPEOF(optimizedThreadSpec)>;
307
308 uint32_t blockDynSharedMemBytes = onHost::getDynSharedMemBytes(threadSpec, kernelBundle);
309
310 kernelName<<<
311 convertVecToUniformCudaHipDim(numBlocks),
312 convertVecToUniformCudaHipDim(numThreadsPerBlock),
313 static_cast<std::size_t>(blockDynSharedMemBytes),
314 queue.getNativeHandle()>>>(kernelBundle, optimizedThreadSpec);
315
316 queue.conditionalWait();
317 }
318 };
319 } // namespace unifiedCudaHip
320} // namespace alpaka::onHost
321
322namespace alpaka::internal
323{
324 template<typename T_Device>
325 struct GetApi::Op<onHost::unifiedCudaHip::Queue<T_Device>>
326 {
327 inline constexpr auto operator()(auto&& queue) const
328 {
329 return getApi(queue.m_device);
330 }
331 };
332} // namespace alpaka::internal
333
334namespace alpaka::onHost
335{
336 namespace internal
337 {
338 template<typename T_Device, typename T_Task>
339 struct Enqueue::HostTask<unifiedCudaHip::Queue<T_Device>, T_Task>
340 {
341 struct HostFuncData
342 {
343 // We don't need to keep the queue alive, because in its dtor it will synchronize with the CUDA/HIP
344 // stream and wait until all host functions and the CallbackThread are done. It's actually an error to
345 // copy the queue into the host function. Destroying it here would call CUDA/HIP APIs from the host
346 // function. Passing it further to the Callback thread, would make the Callback thread hold a task
347 // containing the queue with the CallbackThread itself. Destroying the task if no other queue instance
348 // exists will make the CallbackThread join itself and crash.
349 unifiedCudaHip::Queue<T_Device>& q;
350 T_Task t;
351 };
352
353 static void uniformCudaHipRtHostFunc(void* arg)
354 {
355 auto data = std::unique_ptr<HostFuncData>(reinterpret_cast<HostFuncData*>(arg));
356 auto& queue = data->q;
357 auto f = queue.m_callBackThread.submit([d = std::move(data)] { d->t(); });
358 f.wait();
359 }
360
361 void operator()(unifiedCudaHip::Queue<T_Device>& queue, T_Task const& task) const
362 {
364 using ApiInterface = typename unifiedCudaHip::Queue<T_Device>::ApiInterface;
365
366 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
367 ApiInterface,
368 ApiInterface::launchHostFunc(
369 queue.getNativeHandle(),
370 uniformCudaHipRtHostFunc,
371 new HostFuncData{queue, task}));
372
373 queue.conditionalWait();
374 }
375 };
376
377 template<typename T_Device, typename T_Task>
378 struct Enqueue::HostTaskDeferred<unifiedCudaHip::Queue<T_Device>, T_Task>
379 {
380 // same as for Enqueue::HostTask, but not waiting for the task to finish
381 struct HostFuncData
382 {
383 unifiedCudaHip::Queue<T_Device>& q;
384 T_Task t;
385 };
386
387 static void uniformCudaHipRtHostFuncAsync(void* arg)
388 {
389 auto data = std::unique_ptr<HostFuncData>(reinterpret_cast<HostFuncData*>(arg));
390 auto& queue = data->q;
391 auto queueDependency = queue.getSharedPtr();
392 queue.m_callBackThread.submit([d = std::move(data), queueDependency] { d->t(); });
393 // don't wait, we're async
394 }
395
396 void operator()(unifiedCudaHip::Queue<T_Device>& queue, T_Task const& task) const
397 {
399 using ApiInterface = typename unifiedCudaHip::Queue<T_Device>::ApiInterface;
400
401 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
402 ApiInterface,
403 ApiInterface::launchHostFunc(
404 queue.getNativeHandle(),
405 uniformCudaHipRtHostFuncAsync,
406 new HostFuncData{queue, task}));
407
408 queue.conditionalWait();
409 }
410 };
411
412 template<typename T_Device, typename T_Event>
413 struct internal::Enqueue::Event<unifiedCudaHip::Queue<T_Device>, T_Event>
414 {
415 void operator()(unifiedCudaHip::Queue<T_Device>& queue, T_Event& event) const
416 {
417 ALPAKA_LOG_FUNCTION(onHost::logger::event + onHost::logger::queue);
418 using ApiInterface = typename unifiedCudaHip::Queue<T_Device>::ApiInterface;
419 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
420 ApiInterface,
421 ApiInterface::eventRecord(event.getNativeHandle(), queue.getNativeHandle()));
422
423 queue.conditionalWait();
424 }
425 };
426
427 template<
428 typename T_Device,
429 alpaka::concepts::UnifiedCudaHipExecutor T_Executor,
430 alpaka::concepts::Vector T_NumBlocks,
431 alpaka::concepts::Vector T_NumThreads,
432 typename T_KernelBundle>
433 struct Enqueue::
434 Kernel<unifiedCudaHip::Queue<T_Device>, ThreadSpec<T_NumBlocks, T_NumThreads, T_Executor>, T_KernelBundle>
435 {
436 void operator()(
437 unifiedCudaHip::Queue<T_Device>& queue,
439 T_KernelBundle const& kernelBundle) const
440 {
442 unifiedCudaHip::CallKernel{}.template operator()<false>(queue, threadSpec, kernelBundle);
443 }
444 };
445
446 template<
447 typename T_Device,
448 alpaka::concepts::UnifiedCudaHipExecutor T_Executor,
449 typename T_NumFrames,
450 typename T_FrameExtents,
451 typename T_KernelBundle>
452 struct Enqueue::
453 Kernel<unifiedCudaHip::Queue<T_Device>, FrameSpec<T_NumFrames, T_FrameExtents, T_Executor>, T_KernelBundle>
454 {
455 void operator()(
456 unifiedCudaHip::Queue<T_Device>& queue,
457 FrameSpec<T_NumFrames, T_FrameExtents, T_Executor> const& frameSpec,
458 T_KernelBundle const& kernelBundle) const
459 {
460 static_assert(
461 ALPAKA_TYPEOF(frameSpec)::getExecutor() != exec::anyExecutor,
462 "'exec::anyExecutor' can not be used to enqueue an kernel.");
464 auto threadBlocking = internal::adjustThreadSpec(*queue.m_device.get(), frameSpec, kernelBundle);
465 unifiedCudaHip::CallKernel{}.template operator()<true>(queue, threadBlocking, kernelBundle);
466 }
467 };
468
469 template<typename T_Device, typename T_Dest, typename T_Source, typename T_Extents>
470 struct Memcpy::Op<unifiedCudaHip::Queue<T_Device>, T_Dest, T_Source, T_Extents>
471 {
472 /** Perform data copy.
473 *
474 * To understand the usage of pitches to shift pointers within the implementation see
475 * https://alpaka3.readthedocs.io/en/latest/advanced/datastorage.html#pitches
476 */
477 void operator()(
478 unifiedCudaHip::Queue<T_Device>& queue,
479 auto&& dest,
480 T_Source const& src,
481 T_Extents const& extents) const requires std::same_as<ALPAKA_TYPEOF(dest), T_Dest>
482 {
484 using ApiInterface = typename unifiedCudaHip::Queue<T_Device>::ApiInterface;
485
486 // use always 64bit precision to avoid overflows in the pitch calculations
487 auto extentMd = pCast<size_t>(extents);
488
489 if(extentMd.product() == size_t{0u})
490 return;
491
492 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
493 ApiInterface,
494 ApiInterface::setDevice(onHost::getNativeHandle(queue.m_device)));
495
496 void* destPtr = toVoidPtr(onHost::data(dest));
497 auto destPitch = pCast<size_t>(onHost::getPitches(dest));
498 void const* srcPtr = toVoidPtr(onHost::data(src));
499 auto srcPitch = pCast<size_t>(onHost::getPitches(src));
500
501 auto copyKind = unifiedCudaHip::MemcpyKind<
502 ApiInterface,
503 ALPAKA_TYPEOF(alpaka::internal::getApi(dest)),
504 ALPAKA_TYPEOF(alpaka::internal::getApi(src))>::kind;
505
506 constexpr auto dim = alpaka::trait::getDim_v<T_Extents>;
507 if constexpr(dim == 1u)
508 {
509 // Initiate the memory copy.
510 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
511 ApiInterface,
512 ApiInterface::memcpyAsync(
513 destPtr,
514 srcPtr,
515 extentMd.x() * sizeof(alpaka::trait::GetValueType_t<T_Dest>),
516 copyKind,
517 internal::getNativeHandle(queue)));
518 }
519 else if constexpr(dim == 2u)
520 {
521 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
522 ApiInterface,
523 ApiInterface::memcpy2DAsync(
524 destPtr,
525 destPitch.y(),
526 srcPtr,
527 srcPitch.y(),
528 extentMd.x() * sizeof(alpaka::trait::GetValueType_t<T_Dest>),
529 extentMd.y(),
530 copyKind,
531 internal::getNativeHandle(queue)));
532 }
533 else if constexpr(dim == 3u)
534 {
535 using VecIdxType = ALPAKA_TYPEOF(extentMd);
536
537 memcopy3D<alpaka::trait::GetValueType_t<T_Dest>, ApiInterface>(
538 queue,
539 copyKind,
540 // 3D is nativ supported therefore we can handle the memcpy with a single call
541 VecIdxType::fill(1u),
542 // we do not need to adjust the src and dest pointer
543 VecIdxType::fill(0u),
544 VecIdxType::fill(0u),
545 extentMd,
546 destPtr,
547 destPitch,
548 srcPtr,
549 srcPitch);
550 }
551 else if constexpr(dim >= 4u)
552 {
553 // Both src and dest must be contiguous memory after the 3 dimension.
554 bool isContiguous = true;
555 /* Skip the fastest two dimensions.
556 * We need to check that we do not have padding between dimension 3->4 or higher.
557 * Padding in between row and column is no problem because this is supported by CUDA/HIP.
558 */
559 for(uint32_t d = dim - 3u; d >= 1u; --d)
560 {
561 isContiguous = isContiguous && (extentMd[d] * destPitch[d] == destPitch[d - 1u])
562 && (extentMd[d] * srcPitch[d] == srcPitch[d - 1u]);
563 }
564
565 if(isContiguous)
566 {
567 /* If the memory is contiguous in the dimensions higher than 3 we can emulate the N-dimensional
568 * copy with a 3D memcpy by mapping the higher dimensions into z.
569 * This is more efficient than calling the CUDA/HIP copy function multiple times.
570 */
571 alpaka::concepts::Vector<size_t, 3u> auto mappedExtentMd = extentMd.template rshrink<3u>();
572 // remove x,y dimension, fuse all other dimensions into the z component
573 mappedExtentMd.z() = extentMd.template rshrink<dim - 2u>(dim - 3u).product();
574 using VecIdxType = ALPAKA_TYPEOF(mappedExtentMd);
575
576 memcopy3D<alpaka::trait::GetValueType_t<T_Dest>, ApiInterface>(
577 queue,
578 copyKind,
579 // 3D is nativ supported therefore we can handle the memcpy with a single call
580 VecIdxType::fill(1u),
581 VecIdxType::fill(0u),
582 VecIdxType::fill(0u),
583 mappedExtentMd,
584 destPtr,
585 destPitch.template rshrink<3u>(),
586 srcPtr,
587 srcPitch.template rshrink<3u>());
588 }
589 else
590 {
591 // remove the 3 fast moving dimensions
592 auto repetitions = extentMd.template rshrink<dim - 3u>(dim - 4u);
593 auto srcPitchJump = srcPitch.template rshrink<dim - 3u>(dim - 4u);
594 auto destPitchJump = destPitch.template rshrink<dim - 3u>(dim - 4u);
595
596 memcopy3D<alpaka::trait::GetValueType_t<T_Dest>, ApiInterface>(
597 queue,
598 copyKind,
599 repetitions,
600 destPitchJump,
601 srcPitchJump,
602 extentMd,
603 destPtr,
604 destPitch,
605 srcPtr,
606 srcPitch);
607 }
608 }
609
610 queue.conditionalWait();
611 }
612
613 /** Memcopy which calls multiple times the 3D memcpy.
614 *
615 * The copy method is repetitions times repeated and the srcPtr and destPtr is advanced each time by the
616 * corresponding pitches in bytes.
617 *
618 * @param copyKind cuda/hip memcopy kind
619 * @param repetitions how often the 3D memcpy should be called
620 * @param destPitchJump bytes vector with pitches required to jump to the next 3D block to copy for the
621 * destPtr. Dimension must be equal to repetitions.
622 * @param srcPitchJump bytes vector with pitches required to jump to the next 3D block to copy for the
623 * srcPtr. Dimension must be equal to repetitions.
624 * @param extentMd Extents to describe how many elements should be copied. Dimension should be equal to
625 * the original buffer/view dimension.
626 * @param destPitchesOriginal Original pitches of destPtr. Dimension should be equal to the original
627 * buffer/view dimension.
628 * @param srcPitchesOriginal Original pitches of srcPtr. Dimension should be equal to the original
629 * buffer/view dimension.
630 */
631 template<typename T_ValueType, typename T_ApiInterface>
632 void memcopy3D(
633 auto& queue,
634 auto copyKind,
635 alpaka::concepts::Vector<size_t> auto const& repetitions,
636 alpaka::concepts::Vector<size_t> auto const& destPitchJump,
637 alpaka::concepts::Vector<size_t> auto const& srcPitchJump,
638 alpaka::concepts::Vector<size_t> auto const& extentMd,
639 void* destPtr,
640 alpaka::concepts::Vector<size_t> auto const& destPitchesOriginal,
641 void const* srcPtr,
642 alpaka::concepts::Vector<size_t> auto const& srcPitchesOriginal) const
643 {
644 static_assert(
645 ALPAKA_TYPEOF(repetitions)::dim() == ALPAKA_TYPEOF(destPitchJump)::dim()
646 && ALPAKA_TYPEOF(repetitions)::dim() == ALPAKA_TYPEOF(srcPitchJump)::dim());
647 static_assert(
648 ALPAKA_TYPEOF(extentMd)::dim() == ALPAKA_TYPEOF(destPitchesOriginal)::dim()
649 && ALPAKA_TYPEOF(extentMd)::dim() == ALPAKA_TYPEOF(srcPitchesOriginal)::dim());
650
652 repetitions,
653 [&](auto const& idx)
654 {
655 // zero-init required per CUDA documentation
656 typename T_ApiInterface::Memcpy3DParms_t memCpy3DParms{};
657
658 memCpy3DParms.srcPtr = T_ApiInterface::makePitchedPtr(
659 // CUDA/HIP does not support const for pitched pointer
660 const_cast<uint8_t*>(
661 reinterpret_cast<uint8_t const*>(srcPtr) + (idx * srcPitchJump).sum()),
662 srcPitchesOriginal.y(),
663 extentMd.x(),
664 // number of elements in y dimension in the original memory blob
665 srcPitchesOriginal.z() / srcPitchesOriginal.y());
666 memCpy3DParms.dstPtr = T_ApiInterface::makePitchedPtr(
667 reinterpret_cast<uint8_t*>(destPtr) + (idx * destPitchJump).sum(),
668 destPitchesOriginal.y(),
669 extentMd.x(),
670 // number of elements in y dimension in the original memory blob
671 destPitchesOriginal.z() / destPitchesOriginal.y());
672 memCpy3DParms.extent = T_ApiInterface::makeExtent(
673 extentMd.x() * sizeof(T_ValueType),
674 extentMd.y(),
675 extentMd.z());
676 memCpy3DParms.kind = copyKind;
677
678 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
679 T_ApiInterface,
680 T_ApiInterface::memcpy3DAsync(&memCpy3DParms, internal::getNativeHandle(queue)));
681 });
682 }
683 };
684
685 // copy to device global memory
686 template<typename T_Device, typename T_Source, typename T_Storage, typename T>
687 struct internal::MemcpyDeviceGlobal::
688 Op<unifiedCudaHip::Queue<T_Device>, onAcc::internal::GlobalDeviceMemoryWrapper<T_Storage, T>, T_Source>
689 {
690 void operator()(
691 unifiedCudaHip::Queue<T_Device>& queue,
692 onAcc::internal::GlobalDeviceMemoryWrapper<T_Storage, T> dest,
693 auto&& source) const
694 {
696
697 using ApiInterface = typename unifiedCudaHip::Queue<T_Device>::ApiInterface;
698 auto queueApi = alpaka::internal::getApi(queue);
699 auto copyKind = unifiedCudaHip::
700 MemcpyKind<ApiInterface, ALPAKA_TYPEOF(queueApi), ALPAKA_TYPEOF(api::host)>::kind;
701
702 void* destPtr{nullptr};
703 void const* srcPtr{nullptr};
704 if constexpr(std::is_pointer_v<ALPAKA_TYPEOF(source)>)
705 srcPtr = source;
706 else
708
709 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
710 ApiInterface,
711 ApiInterface::getSymbolAddress(reinterpret_cast<void**>(&destPtr), dest.getHandle(queueApi)));
712
713 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
714 ApiInterface,
715 ApiInterface::memcpyAsync(destPtr, srcPtr, sizeof(T), copyKind, internal::getNativeHandle(queue)));
716
717 queue.conditionalWait();
718 }
719 };
720
721 // copy from device global memory
722 template<typename T_Device, typename T_Dest, typename T_Storage, typename T>
723 struct internal::MemcpyDeviceGlobal::
724 Op<unifiedCudaHip::Queue<T_Device>, T_Dest, onAcc::internal::GlobalDeviceMemoryWrapper<T_Storage, T>>
725 {
726 void operator()(
727 unifiedCudaHip::Queue<T_Device>& queue,
728 auto&& dest,
729 onAcc::internal::GlobalDeviceMemoryWrapper<T_Storage, T> source) const
730 {
732
733 using ApiInterface = typename unifiedCudaHip::Queue<T_Device>::ApiInterface;
734 auto queueApi = alpaka::internal::getApi(queue);
735 auto copyKind = unifiedCudaHip::
736 MemcpyKind<ApiInterface, ALPAKA_TYPEOF(api::host), ALPAKA_TYPEOF(queueApi)>::kind;
737
738 void* destPtr{nullptr};
739 if constexpr(std::is_pointer_v<ALPAKA_TYPEOF(dest)>)
740 destPtr = dest;
741 else
743
744 void* srcPtr{nullptr};
745
746 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
747 ApiInterface,
748 ApiInterface::getSymbolAddress(reinterpret_cast<void**>(&srcPtr), source.getHandle(queueApi)));
749
750 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
751 ApiInterface,
752 ApiInterface::memcpyAsync(destPtr, srcPtr, sizeof(T), copyKind, internal::getNativeHandle(queue)));
753
754 queue.conditionalWait();
755 }
756 };
757
758 template<typename T_Device, typename T_Dest, typename T_Extents>
759 struct Memset::Op<unifiedCudaHip::Queue<T_Device>, T_Dest, T_Extents>
760 {
761 /** @attention Do not use `requires std::same_as<ALPAKA_TYPEOF(dest), T_Dest>` here else gcc 11.X
762 * (tested 11.4 and 11.3) will run into an internal compiler segfault during the evaluation of the
763 * constraints */
764 void operator()(
765 unifiedCudaHip::Queue<T_Device>& queue,
766 auto&& dest,
767 uint8_t byteValue,
768 T_Extents const& extents) const requires(std::is_same_v<ALPAKA_TYPEOF(dest), T_Dest>)
769 {
771 using ApiInterface = typename unifiedCudaHip::Queue<T_Device>::ApiInterface;
772
773 // use always 64bit precision to avoid overflows in the pitch calculations
774 auto extentMd = pCast<size_t>(extents);
775 if(extentMd.product() == size_t{0u})
776 return;
777
778 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
779 ApiInterface,
780 ApiInterface::setDevice(onHost::getNativeHandle(queue.m_device)));
781
782 void* destPtr = toVoidPtr(onHost::data(dest));
783 auto destPitch = pCast<size_t>(onHost::getPitches(dest));
784
785 constexpr auto dim = alpaka::trait::getDim_v<T_Extents>;
786 if constexpr(dim == 1u)
787 {
788 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
789 ApiInterface,
790 ApiInterface::memsetAsync(
791 destPtr,
792 static_cast<int>(byteValue),
793 extentMd.x() * sizeof(alpaka::trait::GetValueType_t<T_Dest>),
794 internal::getNativeHandle(queue)));
795 }
796 else if constexpr(dim == 2u)
797 {
798 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
799 ApiInterface,
800 ApiInterface::memset2DAsync(
801 destPtr,
802 destPitch.y(),
803 static_cast<int>(byteValue),
804 extentMd.x() * sizeof(alpaka::trait::GetValueType_t<T_Dest>),
805 extentMd.y(),
806 internal::getNativeHandle(queue)));
807 }
808 else if constexpr(dim == 3u)
809 {
810 using VecIdxType = ALPAKA_TYPEOF(extentMd);
811
812 memset3D<alpaka::trait::GetValueType_t<T_Dest>, ApiInterface>(
813 queue,
814 byteValue,
815 // 3D is nativ supported therefore we can handle the memset with a single call
816 VecIdxType::fill(1u),
817 VecIdxType::fill(0u),
818 extentMd,
819 destPtr,
820 destPitch);
821 }
822 else if constexpr(dim >= 4u)
823 {
824 // dest must be contiguous memory after the 3 dimension
825 bool isContiguous = true;
826 /* Skip the fastest two dimensions.
827 * We need to check that we do not have padding between dimension 3->4 or higher.
828 * Padding in between row and column is no problem because this is supported by CUDA/HIP.
829 */
830 for(uint32_t d = dim - 3u; d >= 1u; --d)
831 isContiguous = isContiguous && (extentMd[d] * destPitch[d] == destPitch[d - 1u]);
832
833 if(isContiguous)
834 {
835 /* If the memory is contiguous in the dimensions higher than 3 we can emulate the N-dimensional
836 * memset with a 3D memset by mapping the higher dimensions into z.
837 * This is more efficient than calling the CUDA/HIP memset function multiple times.
838 */
839 alpaka::concepts::Vector<size_t, 3u> auto mappedExtentMd = extentMd.template rshrink<3u>();
840 // remove x,y dimension, fuse all other dimensions into the z component
841 mappedExtentMd.z() = extentMd.template rshrink<dim - 2u>(dim - 3u).product();
842 using VecIdxType = ALPAKA_TYPEOF(mappedExtentMd);
843
844 memset3D<alpaka::trait::GetValueType_t<T_Dest>, ApiInterface>(
845 queue,
846 byteValue,
847 // 3D is nativ supported therefore we can handle the memset with a single call
848 VecIdxType::fill(1u),
849 VecIdxType::fill(0u),
850 mappedExtentMd,
851 destPtr,
852 destPitch.template rshrink<3u>());
853 }
854 else
855 {
856 auto repetitions = extentMd.template rshrink<dim - 3u>(dim - 4u);
857 auto destPitchJump = destPitch.template rshrink<dim - 3u>(dim - 4u);
858
859 memset3D<alpaka::trait::GetValueType_t<T_Dest>, ApiInterface>(
860 queue,
861 byteValue,
862 repetitions,
863 destPitchJump,
864 extentMd,
865 destPtr,
866 destPitch);
867 }
868 }
869
870 queue.conditionalWait();
871 }
872
873 /** Memset which calls multiple times the 3D memset.
874 *
875 * The memset method is repetitions times repeated and the destPtr is advanced each time by the
876 * corresponding pitches in bytes.
877 *
878 * @param repetitions how often the 3D memset should be called
879 * @param destPitchJump bytes vector with pitches required to jump to the next 3D block to memset for the
880 * destPtr. Dimension must be equal to repetitions.
881 * @param extentMd Extents to describe how many elements should be set. Dimension should be equal to the
882 * original buffer/view dimension.
883 * @param destPitchesOriginal Original pitches of destPtr. Dimension should be equal to the original
884 * buffer/view dimension.
885 */
886 template<typename T_ValueType, typename T_ApiInterface>
887 void memset3D(
888 auto& queue,
889 uint8_t byteValue,
890 alpaka::concepts::Vector<size_t> auto const& repetitions,
891 alpaka::concepts::Vector<size_t> auto const& destPitchJump,
892 alpaka::concepts::Vector<size_t> auto const& extentMd,
893 void* destPtr,
894 alpaka::concepts::Vector<size_t> auto const& destPitchesOriginal) const
895 {
896 static_assert(ALPAKA_TYPEOF(repetitions)::dim() == ALPAKA_TYPEOF(destPitchJump)::dim());
897 static_assert(ALPAKA_TYPEOF(extentMd)::dim() == ALPAKA_TYPEOF(destPitchesOriginal)::dim());
898
900 repetitions,
901 [&](auto const& idx)
902 {
903 auto const pitchedPtrVal = T_ApiInterface::makePitchedPtr(
904 reinterpret_cast<uint8_t*>(destPtr) + (idx * destPitchJump).sum(),
905 destPitchesOriginal.y(),
906 extentMd.x(),
907 destPitchesOriginal.z() / destPitchesOriginal.y());
908
909 auto const extentVal = T_ApiInterface::makeExtent(
910 extentMd.x() * sizeof(T_ValueType),
911 extentMd.y(),
912 extentMd.z());
913
914 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
915 T_ApiInterface,
916 T_ApiInterface::memset3DAsync(
917 pitchedPtrVal,
918 static_cast<int>(byteValue),
919 extentVal,
920 internal::getNativeHandle(queue)));
921 });
922 }
923 };
924
925 template<typename T_Device, typename T_Dest, typename T_Value, typename T_Extents>
926 struct Fill::Op<unifiedCudaHip::Queue<T_Device>, T_Dest, T_Value, T_Extents>
927 {
928 void operator()(
929 unifiedCudaHip::Queue<T_Device>& queue,
930 auto&& dest,
931 T_Value elementValue,
932 T_Extents const& extents) const
933 requires std::same_as<ALPAKA_TYPEOF(dest), T_Dest>
934 && std::same_as<alpaka::trait::GetValueType_t<ALPAKA_TYPEOF(dest)>, T_Value>
935 {
937 // avoid that we pass a SharedBuffer and convert non alpaka data views
938 auto dataView = makeView(dest);
939
940 alpaka::internal::generic::fill(
941 queue,
942 defaultExecutor(getDevice(queue)),
943 dataView.getSubView(extents),
944 elementValue);
945 }
946 };
947
948 /** The code is a copy of the Alloc::Op with the difference that the memory is allocated and freed
949 * within a queue
950 */
951 template<typename T_Type, typename T_Device, alpaka::concepts::Vector T_Extents>
952 struct AllocDeferred::Op<T_Type, unifiedCudaHip::Queue<T_Device>, T_Extents>
953 {
954 auto operator()(unifiedCudaHip::Queue<T_Device>& queue, T_Extents const& extents) const
955 {
957 using ApiInterface = typename T_Device::ApiInterface;
958
959 /** Each CUDA/HIP allocation is aligned to at least 128 byte but typically to 256byte
960 *
961 * @todo check if this value can be derived from the device properties
962 * @todo validate if memory is always aligned to 256 byte
963 */
964 constexpr uint32_t alignment = 128u;
965 auto [memSizeInByte, pitches] = api::util::emulatedAlignedMemDescription<T_Type>(alignment, extents);
966
967 T_Type* ptr = nullptr;
968 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK(
969 ApiInterface,
970 ApiInterface::mallocAsync((void**) &ptr, memSizeInByte, queue.getNativeHandle()));
971
972 queue.conditionalWait();
973
974 auto deviceDependency = onHost::Device{queue.getDevice()->getSharedPtr()};
975 // it is the shared pointer to the internal queue, NOT onHost::Queue
976 auto queueDependency = queue.getSharedPtr();
977
978 auto deleter = [ptr, queueDependency]()
979 {
980 ALPAKA_UNIFORM_CUDA_HIP_RT_CHECK_NOEXCEPT(
981 ApiInterface,
982 ApiInterface::freeAsync(toVoidPtr(ptr), queueDependency->getNativeHandle()));
983 };
984
985 auto sharedBuffer = onHost::SharedBuffer{
986 deviceDependency,
987 ptr,
988 extents,
989 pitches,
990 std::move(deleter),
991 Alignment<alignment>{}};
992 return sharedBuffer;
993 }
994 };
995 } // namespace internal
996} // namespace alpaka::onHost
997#endif
#define ALPAKA_FN_HOST
All functions that can be used on an accelerator have to be attributed with ALPAKA_FN_ACC or ALPAKA_F...
Definition common.hpp:33
#define ALPAKA_TYPEOF(...)
Get the type of instance.
Definition common.hpp:154
#define ALPAKA_FORWARD(instance)
Perfectly forward an instance as argument.
Definition common.hpp:148
#define ALPAKA_LOG_FUNCTION(logLvl)
Log the entry and exit of a scope.
Definition logger.hpp:95
auto emulatedAlignedMemDescription(uint32_t alignmentInByte, T_Extents extents)
provides a memory description to create multidimensional linewise aligned memory within a one dimensi...
Definition util.hpp:101
constexpr auto host
Definition Api.hpp:39
constexpr bool operator!=(alpaka::concepts::Api auto lhs, alpaka::concepts::Api auto rhs)
Definition api.hpp:53
constexpr bool operator==(alpaka::concepts::Api auto lhs, alpaka::concepts::Api auto rhs)
Definition api.hpp:48
constexpr AnyExecutor anyExecutor
Automatic executor selection.
Definition executor.hpp:33
constexpr auto block
Definition tag.hpp:259
constexpr auto thread
Definition tag.hpp:253
auto ndLoopIncIdx(TExtentVec &idx, TExtentVec const &extent, TFnObj const &f) -> void
Loops over an n-dimensional iteration index variable calling f(idx, args...) for each iteration....
Definition NdLoop.hpp:73
constexpr WarpSize warpSize
Definition tag.hpp:42
constexpr DeviceKind deviceKind
Definition tag.hpp:30
constexpr Api api
Definition tag.hpp:24
constexpr Device device
Definition scope.hpp:70
constexpr auto queue
Definition lvl.hpp:127
constexpr auto kernel
Definition lvl.hpp:142
constexpr auto memory
Definition lvl.hpp:112
constexpr auto event
Definition lvl.hpp:97
Functionality which is usable on the host CPU controller thread.
Definition api.hpp:40
auto getNativeHandle(auto const &handle)
Get the native handle of an handle.
constexpr auto defaultExecutor(internal::concepts::DeviceHandle auto deviceHandle)
Select a default executor for the given device.
Definition trait.hpp:169
SharedBuffer(T_Any const &, T_Type *, T_UserExtents const &, T_UserPitches const &, std::invocable<> auto, T_MemAlignment const) -> SharedBuffer< ALPAKA_TYPEOF(getApi(std::declval< T_Any >())), T_Type, typename T_UserPitches::UniVec, T_MemAlignment >
decltype(auto) data(auto &&any)
pointer to data of an object
std::convertible_to< std::string > auto getName(auto &&any)
Runtime name for a given object.
ThreadSpec(T_NumBlocks const &, T_NumThreads const &) -> ThreadSpec< alpaka::trait::getVec_t< T_NumBlocks >, alpaka::trait::getVec_t< T_NumThreads > >
Device(Handle< T_Device > &&) -> Device< ALPAKA_TYPEOF(alpaka::internal::getApi(std::declval< T_Device >())), ALPAKA_TYPEOF(alpaka::internal::getDeviceKind(std::declval< T_Device >()))>
void wait(alpaka::concepts::HasGet auto &handle)
wait for all work to be finished
Queue(Handle< T_Queue > &&, T_QueueKind) -> Queue< Device< ALPAKA_TYPEOF(alpaka::internal::getApi(std::declval< T_Queue >())), ALPAKA_TYPEOF(alpaka::internal::getDeviceKind(std::declval< T_Queue >()))>, T_QueueKind >
decltype(auto) getPitches(auto &&any)
Object pitches.
Definition interface.hpp:55
typename GetValueType< T >::type GetValueType_t
Definition trait.hpp:65
constexpr uint32_t getDim_v
Definition trait.hpp:41
constexpr decltype(auto) getExecutor(auto &&any)
Get the executor associated with an object.
Definition interface.hpp:23
auto * toVoidPtr(T inPtr)
Cast a pointer that may or may not point to volatile memory to a (void*) or (void const*).
Definition util.hpp:34
constexpr decltype(auto) getDeviceKind(auto &&any)
Get the device type of an object.
Definition interface.hpp:78
constexpr decltype(auto) getApi(auto &&any)
Get the API an object depends on.
Definition interface.hpp:42
constexpr auto makeView(auto &&anyWithApi, T_ValueType *pointer, concepts::Vector auto const &extents, T_MemAlignment const memAlignment=T_MemAlignment{})
Definition View.hpp:37
ALPAKA_FN_HOST_ACC Dict(Tuple< DictEntry< T_Keys, T_Values >... > const &) -> Dict< DictEntry< T_Keys, T_Values >... >
constexpr decltype(auto) pCast(auto &&input)
Performs a static_cast on the storage type of combined data type.
Definition cast.hpp:48