openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
target.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: LicenseRef-APL-1.1
3 
10 #ifndef TARGETS_H
11 #define TARGETS_H
12 
13 #include <stdexcept>
14 #include <iostream>
15 #include <string>
16 #include <cstring>
17 #ifdef HAS_ROCM_MODEL
18 #include <hip/hip_runtime.h>
19 #endif
20 #ifdef HAS_CUDA_MODEL
21 #include <cuda_runtime.h>
22 #endif
23 
24 namespace limpet
25 {
26 
30 enum Target {
31  AUTO = -2, // !< special value for chosing the target automatically
32  UNKNOWN = -1,
33  CPU,
38 };
39 
47 Target get_target_from_string(std::string const str);
48 
56 std::string get_string_from_target(Target const target);
57 
62 std::string get_target_list_string();
63 
69 bool is_gpu(Target const target);
70 
79 bool is_concrete(Target const target);
80 
88 template <typename T>
90  typedef T value_type;
91 
99  TargetAllocator(Target target, bool always_managed = false) : _target(target), _always_managed(always_managed) {
100  if (!is_concrete(target)) {
101  throw std::invalid_argument("attempting to construct a TargetAllocator with an invalid target");
102  }
103  }
104 
110  Target get_target() const {
111  return this->_target;
112  }
113 
119  void set_target(Target new_target) {
120  if (!is_concrete(new_target)) {
121  throw std::invalid_argument("attempting to set TargetAllocator to an invalid target");
122  }
123  else {
124  this->_target = new_target;
125  }
126  }
127 
139  T* allocate(std::size_t n, bool do_zero = true) {
140  using type = typename std::conditional<std::is_void<T>::value, char, T>::type;
141  T* ptr = nullptr;
142  std::size_t n_bytes = n * sizeof(type);
143  switch (this->_target) {
144  case Target::MLIR_CPU:
145  case Target::CPU: {
146  std::allocator<type> std_alloc;
147  if(n)
148  {
149  ptr = std_alloc.allocate(n);
150  // Explicitely cast the pointer to silence warning on setting a
151  // dynamic class object to 0.
152  // The memory allocated by this function should be initialized after,
153  // for example by using a placement new
154  if(do_zero)
155  std::memset((void *) ptr, 0, n_bytes);
156  }
157 
158 
159  break;
160  }
161  case Target::MLIR_ROCM:
162 #ifdef HAS_ROCM_MODEL
163  {
164  T* hip_ptr;
165  hipError_t error;
166  // Force usage of HIP managed memory even if it's not supported.
167  // This should still preserve the semantics of managed memory, but
168  // is slower. It can be useful to allocate small shared data between CPU
169  // and GPU.
170  if (this->_always_managed) {
171  error = hipMallocManaged(&hip_ptr, n_bytes);
172  }
173  else {
174  int device;
175  int managed_memory = 0;
176  hipGetDevice(&device);
177  hipDeviceGetAttribute(&managed_memory, hipDeviceAttributeManagedMemory, device);
178  // Check for HIP managed memory and fallback to a normal hipMalloc if
179  // it's not supported. It should still allow the CPU to write to GPU memory.
180  if (managed_memory) {
181  error = hipMallocManaged(&hip_ptr, n_bytes);
182  }
183  else {
184  error = hipMalloc(&hip_ptr, n_bytes);
185  }
186  }
187  if (error != hipSuccess) {
188  throw std::runtime_error(hipGetErrorString(error));
189  }
190  error = hipMemset(hip_ptr, 0, n_bytes);
191  if (error != hipSuccess) {
192  throw std::runtime_error(hipGetErrorString(error));
193  }
194  ptr = hip_ptr;
195  }
196 #else
197  throw std::invalid_argument("target MLIR_ROCM is unavailable");
198 #endif
199  break;
200  case Target::MLIR_CUDA:
201 #ifdef HAS_CUDA_MODEL
202  {
203  T* cuda_ptr;
204  cudaError_t error;
205  // Always use unified memory for CUDA
206  error = cudaMallocManaged(&cuda_ptr, n_bytes);
207  if (error != cudaSuccess) {
208  throw std::runtime_error(cudaGetErrorString(error));
209  }
210  error = cudaMemset(cuda_ptr, 0, n_bytes);
211  if (error != cudaSuccess) {
212  throw std::runtime_error(cudaGetErrorString(error));
213  }
214  ptr = cuda_ptr;
215  }
216 #else
217  throw std::invalid_argument("target MLIR_CUDA is unavailable");
218 #endif
219  break;
220  default:
221  throw std::invalid_argument("unknown allocation target");
222  }
223  return ptr;
224  }
225 
226  // Size is not needed
235  void deallocate(T* ptr, std::size_t n = 0) {
236  using type = typename std::conditional<std::is_void<T>::value, char, T>::type;
237  switch (this->_target) {
238  case Target::MLIR_CPU:
239  case Target::CPU: {
240  std::allocator<type> std_alloc;
241  std_alloc.deallocate((type*) ptr, n);
242  }
243  break;
244  case Target::MLIR_ROCM:
245 #ifdef HAS_ROCM_MODEL
246  {
247  hipError_t error;
248  error = hipFree(ptr);
249  if (error != hipSuccess) {
250  throw std::runtime_error(hipGetErrorString(error));
251  }
252  }
253 #else
254  throw std::invalid_argument("target MLIR_ROCM is unavailable");
255 #endif
256  break;
257  case Target::MLIR_CUDA:
258 #ifdef HAS_CUDA_MODEL
259  {
260  cudaError_t error;
261  error = cudaFree(ptr);
262  if (error != cudaSuccess) {
263  throw std::runtime_error(cudaGetErrorString(error));
264  }
265  }
266 #else
267  throw std::invalid_argument("target MLIR_CUDA is unavailable");
268 #endif
269  break;
270  default:
271  throw std::invalid_argument("unknown allocation target");
272  }
273  }
274 
275  private:
276  Target _target;
277  bool _always_managed;
278 };
279 
288 template<typename T>
289 T* allocate_on_target(Target target, std::size_t n, bool always_managed = false, bool do_zero = true) {
290  TargetAllocator<T> alloc(target, always_managed);
291  return alloc.allocate(n, do_zero);
292 }
302 template<typename T>
303 void deallocate_on_target(Target target, T* ptr) {
304  TargetAllocator<T> alloc(target);
305  return alloc.deallocate(ptr);
306 }
307 
308 } // namespace limpet
309 
310 #endif // TARGETS_H
T * allocate_on_target(Target target, std::size_t n, bool always_managed=false, bool do_zero=true)
Utility function for allocating memory on a target. See TargetAllocator.
Definition: target.h:289
bool is_concrete(Target const target)
Checks if target is a real, concrete target.
Definition: target.cc:56
Target
enum that represents different targets to run ionic models on.
Definition: target.h:30
@ AUTO
Definition: target.h:31
@ MLIR_ROCM
ROCM code for AMD GPUs generated with MLIR.
Definition: target.h:35
@ CPU
baseline CPU model generated with the original opencarp code generator
Definition: target.h:33
@ UNKNOWN
special value to handle unknown targets
Definition: target.h:32
@ MLIR_CUDA
CUDA code for NVIDIA GPUs generated with MLIR.
Definition: target.h:36
@ N_TARGETS
a token to indicate the maximum number of targets
Definition: target.h:37
@ MLIR_CPU
vectorized CPU code generated with MLIR
Definition: target.h:34
std::string get_string_from_target(Target const target)
Get a string representation of a given target.
Definition: target.cc:34
std::string get_target_list_string()
Returns a string containing the list of available targets.
Definition: target.cc:43
bool is_gpu(Target const target)
Checks if this is a GPU target.
Definition: target.cc:52
void deallocate_on_target(Target target, T *ptr)
Utility function for deallocating memory on a target. See TargetAllocator.
Definition: target.h:303
Target get_target_from_string(std::string const str)
Returns a value from the Target enum from a given string.
Definition: target.cc:24
Allocator structure for dynamically allocating memory on multiple targets.
Definition: target.h:89
T * allocate(std::size_t n, bool do_zero=true)
Allocate memory for type T.
Definition: target.h:139
void deallocate(T *ptr, std::size_t n=0)
Deallocate memory pointed by ptr.
Definition: target.h:235
Target get_target() const
Get the target for this allocator.
Definition: target.h:110
TargetAllocator(Target target, bool always_managed=false)
Construct a TargetAllocator.
Definition: target.h:99
void set_target(Target new_target)
Set a new target for this allocator.
Definition: target.h:119
T value_type
type to allocate
Definition: target.h:90