The Gaudi Framework  master (cc9a61f4)
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 auto start_time = std::chrono::steady_clock::now();
53 do {
54 err = cudaMallocAsync( &devPtr, size, stream );
55 if ( err == cudaSuccess ) { break; }
56 if ( err == cudaErrorMemoryAllocation && parent != nullptr ) {
57 // Waiting and retrying only works if we're in an asynchronous algorithm
58 cudaGetLastError();
59 std::unique_lock lck( gpu_mem_mtx );
60 gpu_mem_cv.wait( lck );
61 } else {
62 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
63 }
64 } while ( err == cudaErrorMemoryAllocation );
65 // In case we suspended
66 if ( parent != nullptr ) { parent->restoreAfterSuspend().orThrow( "Error restoring", DEVARREXC ); }
67 return devPtr;
68 }
69
70 void* allocateNoStream( std::size_t size, Gaudi::AsynchronousAlgorithm* parent ) {
71 void* devPtr = nullptr;
72 cudaError_t err = cudaSuccess;
73 auto start_time = std::chrono::steady_clock::now();
74 do {
75 err = cudaMalloc( &devPtr, size );
76 if ( err == cudaSuccess ) { break; }
77 if ( err == cudaErrorMemoryAllocation ) {
78 cudaGetLastError();
79 // If called from an AsynchronousAlgorithm, wait as in the with stream variant
80 // Otherwise, the thread should sleep
81 if ( parent != nullptr ) {
82 std::unique_lock lck( gpu_mem_mtx );
83 gpu_mem_cv.wait( lck );
84 parent->restoreAfterSuspend().orThrow( "Error restoring", DEVARREXC );
85 } else {
86 std::this_thread::sleep_for( 100ms );
87 }
88 } else {
89 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
90 }
91 } while ( err == cudaErrorMemoryAllocation );
92 return devPtr;
93 }
94
95 void freeWithStream( void* ptr, Stream& stream ) {
96 cudaError_t err = cudaFreeAsync( ptr, stream );
97 if ( err != cudaSuccess ) {
98 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
99 }
100 gpu_mem_cv.notify_all();
101 }
102
103 void freeNoStream( void* ptr ) {
104 cudaError_t err = cudaFree( ptr );
105 if ( err != cudaSuccess ) {
106 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
107 }
108 gpu_mem_cv.notify_all();
109 }
110
111 void copyHostToDeviceWithStream( void* devPtr, const void* hstPtr, std::size_t size, Stream& stream ) {
112 cudaError_t err = cudaMemcpyAsync( devPtr, hstPtr, size, cudaMemcpyHostToDevice, stream );
113 if ( err != cudaSuccess ) {
114 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
115 }
116 // await stream to avoid deleting host memory before copy is done
117 stream.await().orThrow( "Await error", DEVARREXC );
118 }
119
120 void copyHostToDeviceNoStream( void* devPtr, const void* hstPtr, std::size_t size ) {
121 cudaError_t err = cudaMemcpy( devPtr, hstPtr, size, cudaMemcpyHostToDevice );
122 if ( err != cudaSuccess ) {
123 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
124 }
125 }
126
127 void copyDeviceToHostWithStream( void* hstPtr, const void* devPtr, std::size_t size, Stream& stream ) {
128 cudaError_t err = cudaMemcpyAsync( hstPtr, devPtr, size, cudaMemcpyDeviceToHost, stream );
129 if ( err != cudaSuccess ) {
130 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
131 }
132 // await stream to avoid deleting host memory before copy is done
133 stream.await().orThrow( "Await error", DEVARREXC );
134 }
135
136 void copyDeviceToHostNoStream( void* hstPtr, const void* devPtr, std::size_t size ) {
137 cudaError_t err = cudaMemcpy( hstPtr, devPtr, size, cudaMemcpyDeviceToHost );
138 if ( err != cudaSuccess ) {
139 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
140 }
141 }
142
143 void copyDeviceToDeviceWithStream( void* destDevPtr, const void* srcDevPtr, std::size_t size, Stream& stream ) {
144 cudaError_t err = cudaMemcpyAsync( destDevPtr, srcDevPtr, size, cudaMemcpyDeviceToDevice, stream );
145 if ( err != cudaSuccess ) {
146 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
147 }
148 }
149
150 void copyDeviceToDeviceNoStream( void* destDevPtr, const void* srcDevPtr, std::size_t size ) {
151 cudaError_t err = cudaMemcpy( destDevPtr, srcDevPtr, size, cudaMemcpyDeviceToDevice );
152 if ( err != cudaSuccess ) {
153 throw GaudiException( err_fmt( err, __FILE__, __LINE__ ), DEVARREXC, StatusCode::FAILURE );
154 }
155 }
156} // namespace Gaudi::CUDA::Detail
Base class for asynchronous algorithms.
virtual StatusCode restoreAfterSuspend() const
Restore after suspend.
Define general base for Gaudi exception.
const StatusCode & orThrow(std::string_view message, std::string_view tag) const
Throw a GaudiException in case of failures.
Definition StatusCode.h:194
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)