BioDynaMo  v1.05.119-a4ff3934
gpu_helper.cc
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------
2 //
3 // Copyright (C) 2021 CERN & University of Surrey for the benefit of the
4 // BioDynaMo collaboration. All Rights Reserved.
5 //
6 // Licensed under the Apache License, Version 2.0 (the "License");
7 // you may not use this file except in compliance with the License.
8 //
9 // See the LICENSE file distributed with this work for details.
10 // See the NOTICE file distributed with this work for additional information
11 // regarding copyright ownership.
12 //
13 // -----------------------------------------------------------------------------
14 
15 #include <cstdlib>
16 #include <fstream>
17 #include <sstream>
18 #include <string>
19 #include <vector>
20 
21 #ifdef USE_OPENCL
22 #ifdef __APPLE__
23 #define CL_HPP_ENABLE_EXCEPTIONS
24 #define CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY
25 #define CL_HPP_MINIMUM_OPENCL_VERSION 120
26 #define CL_HPP_TARGET_OPENCL_VERSION 120
27 #include "cl2.hpp"
28 #else
29 #define CL_HPP_ENABLE_EXCEPTIONS
30 #define CL_HPP_MINIMUM_OPENCL_VERSION 210
31 #define CL_HPP_TARGET_OPENCL_VERSION 210
32 #include <CL/cl2.hpp>
33 #endif
34 #endif // USE_OPENCL
35 
36 #ifdef USE_CUDA
37 #include "cuda_runtime_api.h"
38 #endif // USE_CUDA
39 
40 #include "core/gpu/gpu_helper.h"
41 #include "core/param/param.h"
42 #include "core/simulation.h"
43 #include "core/util/log.h"
44 
45 namespace bdm {
46 
48  static GpuHelper kInstance;
49  return &kInstance;
50 }
51 #ifdef USE_CUDA
52 void GpuHelper::FindGpuDevicesCuda() {
53  auto* param = Simulation::GetActive()->GetParam();
54 
55  int n_devices = 0;
56 
57  cudaGetDeviceCount(&n_devices);
58  if (n_devices == 0) {
59  Log::Fatal("FindGpuDevicesCuda",
60  "No CUDA-compatible GPU found on this machine!");
61  return;
62  }
63 
64  Log::Info("", "Found ", n_devices, " CUDA-compatible GPU device(s): ");
65 
66  for (int i = 0; i < n_devices; i++) {
67  cudaSetDevice(i);
68  cudaDeviceProp prop;
69  cudaGetDeviceProperties(&prop, i);
70  Log::Info("", " [", i, "] ", prop.name);
71  }
72 
73  cudaSetDevice(param->preferred_gpu);
74  cudaDeviceProp prop;
75  cudaGetDeviceProperties(&prop, param->preferred_gpu);
76  Log::Info("", "Selected GPU [", param->preferred_gpu, "]: ", prop.name);
77 }
78 #endif // USE_CUDA
79 
80 #ifdef USE_OPENCL
81 #define ClOk(err) \
82  Simulation::GetActive()->GetOpenCLState()->ClAssert(err, __FILE__, __LINE__, \
83  true);
84 
85 void GpuHelper::CompileOpenCLKernels() {
86  auto* sim = Simulation::GetActive();
87  auto* param = sim->GetParam();
88  auto* ocl_state = sim->GetOpenCLState();
89 
90  std::vector<cl::Program>* all_programs = ocl_state->GetOpenCLProgramList();
91  cl::Context* context = ocl_state->GetOpenCLContext();
92  std::vector<cl::Device>* devices = ocl_state->GetOpenCLDeviceList();
93  // Compile OpenCL program for found device
94  // TODO(ahmad): create more convenient way to compile all OpenCL kernels, by
95  // going through a list of header files. Also, create a stringifier that
96  // goes from .cl --> .h, since OpenCL kernels must be input as a string here
97  std::string bdmsys = std::getenv("BDMSYS");
98  std::ifstream cl_file(
99  bdmsys + "/include/core/gpu/mechanical_forces_op_opencl_kernel.cl");
100  if (cl_file.fail()) {
101  Log::Error("CompileOpenCLKernels", "Kernel file does not exists!");
102  }
103  std::stringstream buffer;
104  buffer << cl_file.rdbuf();
105 
106  cl::Program mechanical_forces_op_program(*context, buffer.str());
107 
108  all_programs->push_back(mechanical_forces_op_program);
109 
110  Log::Info("", "Compiling OpenCL kernels...");
111 
112  std::string options;
113  if (!ocl_state->HasSupportForDouble() &&
114  std::string(kRealtName) == "double") {
115  // We cannot force the GPU code to run floats because the types won't match
116  // with the CPU-sided code (which would expect doubles), causing all kinds
117  // of mismatches in data type sizes
118  Log::Fatal("GpuHelper",
119  "Your GPU does not support double-precision floating point "
120  "operators. In order to use your GPU, consider compiling "
121  "BioDynaMo with -Dreal_t=float");
122  } else {
123  options =
124  Concat("-DBDM_REALT=", kRealtName, " -DBDM_REALT3=", kRealtName, "3");
125  }
126 
127  if (param->opencl_debug) {
128  Log::Info("", "Building OpenCL kernels with debugging symbols");
129  options = Concat(options, " -g -O0");
130  } else {
131  Log::Info("", "Building OpenCL kernels without debugging symbols");
132  }
133 
134  for (auto& prog : *all_programs) {
135  try {
136  prog.build(*devices, options.c_str());
137  } catch (const cl::Error&) {
138  Log::Error("CompileOpenCLKernels", "OpenCL compilation error: ",
139  prog.getBuildInfo<CL_PROGRAM_BUILD_LOG>((*devices)[0]));
140  }
141  }
142 }
143 
144 void GpuHelper::FindGpuDevicesOpenCL() {
145  try {
146  // We keep the context and device list in the resource manager to be
147  // accessible elsewhere to create command queues and buffers from
148  auto* sim = Simulation::GetActive();
149  auto* param = sim->GetParam();
150  auto* ocl_state = sim->GetOpenCLState();
151 
152  cl::Context* context = ocl_state->GetOpenCLContext();
153  cl::CommandQueue* queue = ocl_state->GetOpenCLCommandQueue();
154  std::vector<cl::Device>* devices = ocl_state->GetOpenCLDeviceList();
155 
156  // Get list of OpenCL platforms.
157  std::vector<cl::Platform> platform;
158  // If we get stuck here, it might be because the DISPLAY envar is not set.
159  // Set it to 0 to avoid getting stuck. It's an AMD specific issue
160  cl::Platform::get(&platform);
161 
162  if (platform.empty()) {
163  Log::Error("FindGpuDevicesOpenCL", "No OpenCL platforms found");
164  }
165 
166  // Go over all available platforms and devices until all compatible
167  // devices are found
168  for (auto p = platform.begin(); p != platform.end(); p++) {
169  std::vector<cl::Device> pldev;
170 
171  try {
172  // Only select GPU's
173  p->getDevices(CL_DEVICE_TYPE_GPU, &pldev);
174 
175  for (auto d = pldev.begin(); d != pldev.end(); d++) {
176  if (!d->getInfo<CL_DEVICE_AVAILABLE>()) // NOLINT
177  continue;
178 
179  if (d->getInfo<CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE>()) {
180  ocl_state->EnableSupportForDouble();
181  } else {
182  ocl_state->DisableSupportForDouble();
183  }
184 
185  devices->push_back(*d);
186  }
187  } catch (...) {
188  Log::Error("FindGpuDevicesOpenCL",
189  "Found bad OpenCL platform! Continuing to next one");
190  devices->clear();
191  continue;
192  }
193  }
194 
195  if (devices->empty()) {
196  Log::Fatal("FindGpuDevicesOpenCL",
197  "No OpenCL compatible GPU's found on this machine!");
198  return;
199  }
200 
201  *context = cl::Context(*devices);
202 
203  Log::Info("", "Found ", devices->size(),
204  " OpenCL-compatible GPU device(s): ");
205 
206  for (size_t i = 0; i < devices->size(); i++) {
207  Log::Info("", " [", i, "] ", (*devices)[i].getInfo<CL_DEVICE_NAME>());
208  }
209 
210  int selected_gpu = param->preferred_gpu;
211  // If not specifically selected, we select the first GPU with double support
212  if (selected_gpu < 0) {
213  auto itr = std::find(ocl_state->GetFp64Support()->begin(),
214  ocl_state->GetFp64Support()->end(), true);
215  if (itr == ocl_state->GetFp64Support()->end()) {
216  Log::Fatal("FindGpuDevicesOpenCL",
217  "No OpenCL compatible GPU's with double precision support "
218  "found on this machine!");
219  return;
220  } else {
221  selected_gpu = std::distance(ocl_state->GetFp64Support()->begin(), itr);
222  }
223  }
224  Log::Info("", "Selected GPU [", selected_gpu,
225  "]: ", (*devices)[selected_gpu].getInfo<CL_DEVICE_NAME>());
226 
227  ocl_state->SelectGpu(selected_gpu);
228 
229  // Create command queue for that GPU
230  cl_int queue_err;
231  *queue = cl::CommandQueue(*context, (*devices)[selected_gpu],
232  CL_QUEUE_PROFILING_ENABLE, &queue_err);
233  ClOk(queue_err);
234 
235  // Compile the OpenCL kernels
236  CompileOpenCLKernels();
237  } catch (const cl::Error& err) {
238  Log::Error("FindGpuDevicesOpenCL", "OpenCL error: ", err.what(), "(",
239  err.err(), ")");
240  }
241 }
242 #endif // defined(USE_OPENCL)
243 
245 #if (defined(USE_CUDA) || defined(USE_OPENCL))
246  auto* param = Simulation::GetActive()->GetParam();
247  if (param->compute_target == "opencl") {
248 #ifdef USE_OPENCL
249  GpuHelper::FindGpuDevicesOpenCL();
250 #else
251  Log::Fatal("InitializeGPUEnvironment",
252  "You tried to use the GPU (OpenCL) version of BioDynaMo, but no "
253  "OpenCL installation was detected on this machine. Switching to "
254  "the CPU version...");
255 #endif // USE_OPENCL
256  } else {
257 #ifdef USE_CUDA
258  GpuHelper::FindGpuDevicesCuda();
259 #else
260  Log::Fatal("InitializeGPUEnvironment",
261  "You tried to use the GPU (CUDA) version of BioDynaMo, but no "
262  "CUDA installation was detected on this machine. Switching to "
263  "the CPU version...");
264 #endif // USE_CUDA
265  }
266 #endif // defined(USE_CUDA) || defined(USE_OPENCL)
267 }
268 
269 } // namespace bdm
bdm::GpuHelper::GetInstance
static GpuHelper * GetInstance()
Definition: gpu_helper.cc:47
bdm::GpuHelper
Definition: gpu_helper.h:20
bdm
Definition: agent.cc:39
gpu_helper.h
bdm::GpuHelper::InitializeGPUEnvironment
void InitializeGPUEnvironment()
Definition: gpu_helper.cc:244
bdm::Log::Info
static void Info(const std::string &location, const Args &... parts)
Prints information message.
Definition: log.h:55
bdm::kRealtName
constexpr const char * kRealtName
Definition: real_t.h:22
bdm::Concat
std::string Concat(const Args &... parts)
Concatenates all arguments into a string. Equivalent to streaming all arguments into a stringstream a...
Definition: string.h:70
bdm::Log::Fatal
static void Fatal(const std::string &location, const Args &... parts)
Prints fatal error message.
Definition: log.h:115
bdm::Log::Error
static void Error(const std::string &location, const Args &... parts)
Prints error message.
Definition: log.h:79
log.h
simulation.h
bdm::Simulation::GetParam
const Param * GetParam() const
Returns the simulation parameters.
Definition: simulation.cc:254
param.h
bdm::Simulation::GetActive
static Simulation * GetActive()
This function returns the currently active Simulation simulation.
Definition: simulation.cc:68