The Gaudi Framework  master (57817a18)
Loading...
Searching...
No Matches
CUDADeviceArray.cpp
Go to the documentation of this file.
1/***********************************************************************************\
2* (c) Copyright 2024-2026 CERN for the benefit of the LHCb and ATLAS collaborations *
3* *
4* This software is distributed under the terms of the Apache version 2 licence, *
5* copied verbatim in the file "LICENSE". *
6* *
7* In applying this licence, CERN does not waive the privileges and immunities *
8* granted to it by virtue of its status as an Intergovernmental Organization *
9* or submit itself to any jurisdiction. *
10\***********************************************************************************/
11#include "CUDADeviceArray.h"
12
13// Gaudi
16
17// CUDA
18#ifndef __CUDACC__
19# include <cuda_runtime.h>
20#endif
21
22// Fibers
23#include <boost/fiber/condition_variable.hpp>
24#include <boost/fiber/mutex.hpp>
25
26// standard library
27#include <chrono>
28#include <format>
29#include <string>
30#include <thread>
31
33 using namespace std::chrono_literals;
34 namespace {
35 const std::string DEVARREXC = "CUDADeviceArrayException";
36 std::string err_fmt( cudaError_t err, std::string file, int line ) {
37 const char* errname = cudaGetErrorName( err );
38 const char* errstr = cudaGetErrorString( err );
39 std::string errmsg =
40 std::format( "Encountered CUDA error {} [{}]: {} on {}:{}", errname, int( err ), errstr, file, line );
41 return errmsg;
42 }
43
44 boost::fibers::mutex gpu_mem_mtx;
45 boost::fibers::condition_variable gpu_mem_cv;
46 } // namespace
47
48 void* allocateWithStream( std::size_t size, Stream& stream ) {
49 const Gaudi::AsynchronousAlgorithm* parent = stream.asyncParent();
50 void* devPtr = nullptr;
51 cudaError_t err = cudaSuccess;
52 do {
53 err = cudaMallocAsync( &devPtr, size, stream );
54 if ( err == cudaSuccess ) { break; }
55 if ( err == cudaErrorMemoryAllocation && parent != nullptr ) {
56 // Waiting and retrying only works if we're in an asynchronous algorithm
57 cudaGetLastError();
58 parent
59 ->decorateSuspension( [&]() {
60 std::unique_lock lck( gpu_mem_mtx );
61 gpu_mem_cv.wait( lck );
63 } )
64 .orThrow( "Error suspending while waiting for memory", DEVARREXC );
65 } else {
66 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
67 }
68 } while ( err == cudaErrorMemoryAllocation );
69 return devPtr;
70 }
71
72 void* allocateNoStream( std::size_t size, Gaudi::AsynchronousAlgorithm* parent ) {
73 void* devPtr = nullptr;
74 cudaError_t err = cudaSuccess;
75 do {
76 err = cudaMalloc( &devPtr, size );
77 if ( err == cudaSuccess ) { break; }
78 if ( err == cudaErrorMemoryAllocation ) {
79 cudaGetLastError();
80 // If called from an AsynchronousAlgorithm, wait as in the with stream variant
81 // Otherwise, the thread should sleep
82 if ( parent != nullptr ) {
83 parent
84 ->decorateSuspension( [&]() {
85 std::unique_lock lck( gpu_mem_mtx );
86 gpu_mem_cv.wait( lck );
88 } )
89 .orThrow( "Error supending while waiting for memory", DEVARREXC );
90 } else {
91 std::this_thread::sleep_for( 100ms );
92 }
93 } else {
94 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
95 }
96 } while ( err == cudaErrorMemoryAllocation );
97 return devPtr;
98 }
99
100 void freeWithStream( void* ptr, Stream& stream ) {
101 cudaError_t err = cudaFreeAsync( ptr, stream );
102 if ( err != cudaSuccess ) {
103 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
104 }
105 gpu_mem_cv.notify_all();
106 }
107
108 void freeNoStream( void* ptr ) {
109 cudaError_t err = cudaFree( ptr );
110 if ( err != cudaSuccess ) {
111 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
112 }
113 gpu_mem_cv.notify_all();
114 }
115
116 void copyHostToDeviceWithStream( void* devPtr, const void* hstPtr, std::size_t size, Stream& stream ) {
117 cudaError_t err = cudaMemcpyAsync( devPtr, hstPtr, size, cudaMemcpyHostToDevice, stream );
118 if ( err != cudaSuccess ) {
119 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
120 }
121 // await stream to avoid deleting host memory before copy is done
122 stream.await().orThrow( "Await error", DEVARREXC );
123 }
124
125 void copyHostToDeviceNoStream( void* devPtr, const void* hstPtr, std::size_t size ) {
126 cudaError_t err = cudaMemcpy( devPtr, hstPtr, size, cudaMemcpyHostToDevice );
127 if ( err != cudaSuccess ) {
128 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
129 }
130 }
131
132 void copyDeviceToHostWithStream( void* hstPtr, const void* devPtr, std::size_t size, Stream& stream ) {
133 cudaError_t err = cudaMemcpyAsync( hstPtr, devPtr, size, cudaMemcpyDeviceToHost, stream );
134 if ( err != cudaSuccess ) {
135 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
136 }
137 // await stream to avoid deleting host memory before copy is done
138 stream.await().orThrow( "Await error", DEVARREXC );
139 }
140
141 void copyDeviceToHostNoStream( void* hstPtr, const void* devPtr, std::size_t size ) {
142 cudaError_t err = cudaMemcpy( hstPtr, devPtr, size, cudaMemcpyDeviceToHost );
143 if ( err != cudaSuccess ) {
144 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
145 }
146 }
147
148 void copyDeviceToDeviceWithStream( void* destDevPtr, const void* srcDevPtr, std::size_t size, Stream& stream ) {
149 cudaError_t err = cudaMemcpyAsync( destDevPtr, srcDevPtr, size, cudaMemcpyDeviceToDevice, stream );
150 if ( err != cudaSuccess ) {
151 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
152 }
153 }
154
155 void copyDeviceToDeviceNoStream( void* destDevPtr, const void* srcDevPtr, std::size_t size ) {
156 cudaError_t err = cudaMemcpy( destDevPtr, srcDevPtr, size, cudaMemcpyDeviceToDevice );
157 if ( err != cudaSuccess ) {
158 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
159 }
160 }
161} // namespace Gaudi::CUDA::Detail
Base class for asynchronous algorithms.
StatusCode decorateSuspension(std::function< StatusCode()> f) const
Decorate a direct call to a suspending (or possibly suspending) function with the necessary pre- and ...
Define general base for Gaudi exception.
constexpr static const auto SUCCESS
Definition StatusCode.h:99
constexpr static const auto FAILURE
Definition StatusCode.h:100
void copyDeviceToDeviceNoStream(void *destDevPtr, const void *srcDevPtr, std::size_t size)
void * allocateNoStream(std::size_t size, Gaudi::AsynchronousAlgorithm *parent)
void copyDeviceToHostWithStream(void *hstPtr, const void *devPtr, std::size_t size, Stream &stream)
void copyHostToDeviceNoStream(void *devPtr, const void *hstPtr, std::size_t size)
void * allocateWithStream(std::size_t size, Stream &stream)
void copyDeviceToHostNoStream(void *hstPtr, const void *devPtr, std::size_t size)
void copyDeviceToDeviceWithStream(void *destDevPtr, const void *srcDevPtr, std::size_t size, Stream &stream)
void freeNoStream(void *ptr)
void copyHostToDeviceWithStream(void *devPtr, const void *hstPtr, std::size_t size, Stream &stream)
void freeWithStream(void *ptr, Stream &stream)