2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2010,2011,2012,2013,2014,2015,2016, The GROMACS development team.
5 * Copyright (c) 2017,2018,2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
37 * \brief Define functions for detection and initialization for CUDA devices.
39 * \author Szilard Pall <pall.szilard@gmail.com>
44 #include "gpu_utils.h"
50 #include <cuda_profiler_api.h>
52 #include "gromacs/gpu_utils/cudautils.cuh"
53 #include "gromacs/gpu_utils/pmalloc_cuda.h"
54 #include "gromacs/hardware/gpu_hw_info.h"
55 #include "gromacs/utility/basedefinitions.h"
56 #include "gromacs/utility/cstringutil.h"
57 #include "gromacs/utility/exceptions.h"
58 #include "gromacs/utility/fatalerror.h"
59 #include "gromacs/utility/gmxassert.h"
60 #include "gromacs/utility/logger.h"
61 #include "gromacs/utility/programcontext.h"
62 #include "gromacs/utility/smalloc.h"
63 #include "gromacs/utility/snprintf.h"
64 #include "gromacs/utility/stringutil.h"
67 * Max number of devices supported by CUDA (for consistency checking).
69 * In reality it is 16 with CUDA <=v5.0, but let's stay on the safe side.
71 static int cuda_max_device_count = 32;
73 static bool cudaProfilerRun = ((getenv("NVPROF_ID") != nullptr));
75 /** Dummy kernel used for sanity checking. */
76 static __global__ void k_dummy_test(void) {}
78 static cudaError_t checkCompiledTargetCompatibility(int deviceId, const cudaDeviceProp& deviceProp)
80 cudaFuncAttributes attributes;
81 cudaError_t stat = cudaFuncGetAttributes(&attributes, k_dummy_test);
83 if (cudaErrorInvalidDeviceFunction == stat)
86 "\nWARNING: The %s binary does not include support for the CUDA architecture of "
87 "the GPU ID #%d (compute capability %d.%d) detected during detection. "
88 "By default, GROMACS supports all architectures of compute "
89 "capability >= 3.0, so your GPU "
90 "might be rare, or some architectures were disabled in the build. \n"
91 "Consult the install guide for how to use the GMX_CUDA_TARGET_SM and "
92 "GMX_CUDA_TARGET_COMPUTE CMake variables to add this architecture. \n",
93 gmx::getProgramContext().displayName(), deviceId, deviceProp.major, deviceProp.minor);
99 bool isHostMemoryPinned(const void* h_ptr)
101 cudaPointerAttributes memoryAttributes;
102 cudaError_t stat = cudaPointerGetAttributes(&memoryAttributes, h_ptr);
107 case cudaSuccess: result = true; break;
109 case cudaErrorInvalidValue:
110 // If the buffer was not pinned, then it will not be recognized by CUDA at all
112 // Reset the last error status
116 default: CU_RET_ERR(stat, "Unexpected CUDA error");
122 * \brief Runs GPU sanity checks.
124 * Runs a series of checks to determine that the given GPU and underlying CUDA
125 * driver/runtime functions properly.
127 * \param[in] dev_id the device ID of the GPU or -1 if the device has already been initialized
128 * \param[in] dev_prop The device properties structure
129 * \returns 0 if the device looks OK, -1 if it sanity checks failed, and -2 if the device is busy
131 * TODO: introduce errors codes and handle errors more smoothly.
133 static int do_sanity_checks(int dev_id, const cudaDeviceProp& dev_prop)
138 cu_err = cudaGetDeviceCount(&dev_count);
139 if (cu_err != cudaSuccess)
141 fprintf(stderr, "Error %d while querying device count: %s\n", cu_err, cudaGetErrorString(cu_err));
145 /* no CUDA compatible device at all */
151 /* things might go horribly wrong if cudart is not compatible with the driver */
152 if (dev_count < 0 || dev_count > cuda_max_device_count)
157 if (dev_id == -1) /* device already selected let's not destroy the context */
159 cu_err = cudaGetDevice(&id);
160 if (cu_err != cudaSuccess)
162 fprintf(stderr, "Error %d while querying device id: %s\n", cu_err, cudaGetErrorString(cu_err));
169 if (id > dev_count - 1) /* pfff there's no such device */
172 "The requested device with id %d does not seem to exist (device count=%d)\n",
178 /* both major & minor is 9999 if no CUDA capable devices are present */
179 if (dev_prop.major == 9999 && dev_prop.minor == 9999)
183 /* we don't care about emulation mode */
184 if (dev_prop.major == 0)
191 cu_err = cudaSetDevice(id);
192 if (cu_err != cudaSuccess)
194 fprintf(stderr, "Error %d while switching to device #%d: %s\n", cu_err, id,
195 cudaGetErrorString(cu_err));
200 cu_err = checkCompiledTargetCompatibility(dev_id, dev_prop);
201 // Avoid triggering an error if GPU devices are in exclusive or prohibited mode;
202 // it is enough to check for cudaErrorDevicesUnavailable only here because
203 // if we encounter it that will happen in cudaFuncGetAttributes in the above function.
204 if (cu_err == cudaErrorDevicesUnavailable)
208 else if (cu_err != cudaSuccess)
213 /* try to execute a dummy kernel */
216 KernelLaunchConfig config;
217 config.blockSize[0] = 512;
218 const auto dummyArguments = prepareGpuKernelArguments(k_dummy_test, config);
219 launchGpuKernel(k_dummy_test, config, nullptr, "Dummy kernel", dummyArguments);
221 catch (gmx::GromacsException& ex)
223 // launchGpuKernel error is not fatal and should continue with marking the device bad
225 "Error occurred while running dummy kernel sanity check on device #%d:\n %s\n", id,
226 formatExceptionMessageToString(ex).c_str());
230 if (cudaDeviceSynchronize() != cudaSuccess)
235 /* destroy context if we created one */
238 cu_err = cudaDeviceReset();
239 CU_RET_ERR(cu_err, "cudaDeviceReset failed");
245 void init_gpu(const gmx_device_info_t* deviceInfo)
251 stat = cudaSetDevice(deviceInfo->id);
252 if (stat != cudaSuccess)
254 auto message = gmx::formatString("Failed to initialize GPU #%d", deviceInfo->id);
255 CU_RET_ERR(stat, message.c_str());
260 fprintf(stderr, "Initialized GPU ID #%d: %s\n", deviceInfo->id, deviceInfo->prop.name);
264 void free_gpu(const gmx_device_info_t* deviceInfo)
266 // One should only attempt to clear the device context when
267 // it has been used, but currently the only way to know that a GPU
268 // device was used is that deviceInfo will be non-null.
269 if (deviceInfo == nullptr)
279 stat = cudaGetDevice(&gpuid);
280 CU_RET_ERR(stat, "cudaGetDevice failed");
281 fprintf(stderr, "Cleaning up context on GPU ID #%d\n", gpuid);
284 stat = cudaDeviceReset();
285 if (stat != cudaSuccess)
287 gmx_warning("Failed to free GPU #%d: %s", deviceInfo->id, cudaGetErrorString(stat));
291 gmx_device_info_t* getDeviceInfo(const gmx_gpu_info_t& gpu_info, int deviceId)
293 if (deviceId < 0 || deviceId >= gpu_info.n_dev)
295 gmx_incons("Invalid GPU deviceId requested");
297 return &gpu_info.gpu_dev[deviceId];
300 /*! \brief Returns true if the gpu characterized by the device properties is
301 * supported by the native gpu acceleration.
303 * \param[in] dev_prop the CUDA device properties of the gpus to test.
304 * \returns true if the GPU properties passed indicate a compatible
305 * GPU, otherwise false.
307 static bool is_gmx_supported_gpu(const cudaDeviceProp& dev_prop)
309 return (dev_prop.major >= 3);
312 /*! \brief Checks if a GPU with a given ID is supported by the native GROMACS acceleration.
314 * Returns a status value which indicates compatibility or one of the following
315 * errors: incompatibility or insanity (=unexpected behavior).
317 * As the error handling only permits returning the state of the GPU, this function
318 * does not clear the CUDA runtime API status allowing the caller to inspect the error
319 * upon return. Note that this also means it is the caller's responsibility to
320 * reset the CUDA runtime state.
322 * \param[in] deviceId the ID of the GPU to check.
323 * \param[in] deviceProp the CUDA device properties of the device checked.
324 * \returns the status of the requested device
326 static int is_gmx_supported_gpu_id(int deviceId, const cudaDeviceProp& deviceProp)
328 if (!is_gmx_supported_gpu(deviceProp))
330 return egpuIncompatible;
333 /* TODO: currently we do not make a distinction between the type of errors
334 * that can appear during sanity checks. This needs to be improved, e.g if
335 * the dummy test kernel fails to execute with a "device busy message" we
336 * should appropriately report that the device is busy instead of insane.
338 const int checkResult = do_sanity_checks(deviceId, deviceProp);
341 case 0: return egpuCompatible;
342 case -1: return egpuInsane;
343 case -2: return egpuUnavailable;
345 GMX_RELEASE_ASSERT(false, "Invalid do_sanity_checks() return value");
346 return egpuCompatible;
350 bool isGpuDetectionFunctional(std::string* errorMessage)
353 int driverVersion = -1;
354 stat = cudaDriverGetVersion(&driverVersion);
355 GMX_ASSERT(stat != cudaErrorInvalidValue,
356 "An impossible null pointer was passed to cudaDriverGetVersion");
359 gmx::formatString("An unexpected value was returned from cudaDriverGetVersion %s: %s",
360 cudaGetErrorName(stat), cudaGetErrorString(stat))
362 bool foundDriver = (driverVersion > 0);
365 // Can't detect GPUs if there is no driver
366 if (errorMessage != nullptr)
368 errorMessage->assign("No valid CUDA driver found");
374 stat = cudaGetDeviceCount(&numDevices);
375 if (stat != cudaSuccess)
377 if (errorMessage != nullptr)
379 /* cudaGetDeviceCount failed which means that there is
380 * something wrong with the machine: driver-runtime
381 * mismatch, all GPUs being busy in exclusive mode,
382 * invalid CUDA_VISIBLE_DEVICES, or some other condition
383 * which should result in GROMACS issuing at least a
385 errorMessage->assign(cudaGetErrorString(stat));
388 // Consume the error now that we have prepared to handle
389 // it. This stops it reappearing next time we check for
390 // errors. Note that if CUDA_VISIBLE_DEVICES does not contain
391 // valid devices, then cudaGetLastError returns the
392 // (undocumented) cudaErrorNoDevice, but this should not be a
393 // problem as there should be no future CUDA API calls.
394 // NVIDIA bug report #2038718 has been filed.
400 // We don't actually use numDevices here, that's not the job of
405 void findGpus(gmx_gpu_info_t* gpu_info)
409 gpu_info->n_dev_compatible = 0;
412 cudaError_t stat = cudaGetDeviceCount(&ndev);
413 if (stat != cudaSuccess)
415 GMX_THROW(gmx::InternalError(
416 "Invalid call of findGpus() when CUDA API returned an error, perhaps "
417 "canDetectGpus() was not called appropriately beforehand."));
420 // We expect to start device support/sanity checks with a clean runtime error state
421 gmx::ensureNoPendingCudaError("");
423 gmx_device_info_t* devs;
425 for (int i = 0; i < ndev; i++)
428 memset(&prop, 0, sizeof(cudaDeviceProp));
429 stat = cudaGetDeviceProperties(&prop, i);
431 if (stat != cudaSuccess)
433 // Will handle the error reporting below
434 checkResult = egpuInsane;
438 checkResult = is_gmx_supported_gpu_id(i, prop);
443 devs[i].stat = checkResult;
445 if (checkResult == egpuCompatible)
447 gpu_info->n_dev_compatible++;
452 // - we inspect the CUDA API state to retrieve and record any
453 // errors that occurred during is_gmx_supported_gpu_id() here,
454 // but this would be more elegant done within is_gmx_supported_gpu_id()
455 // and only return a string with the error if one was encountered.
456 // - we'll be reporting without rank information which is not ideal.
457 // - we'll end up warning also in cases where users would already
458 // get an error before mdrun aborts.
460 // Here we also clear the CUDA API error state so potential
461 // errors during sanity checks don't propagate.
462 if ((stat = cudaGetLastError()) != cudaSuccess)
464 gmx_warning("An error occurred while sanity checking device #%d; %s: %s",
465 devs[i].id, cudaGetErrorName(stat), cudaGetErrorString(stat));
470 stat = cudaPeekAtLastError();
471 GMX_RELEASE_ASSERT(stat == cudaSuccess,
472 gmx::formatString("We promise to return with clean CUDA state, but "
473 "non-success state encountered: %s: %s",
474 cudaGetErrorName(stat), cudaGetErrorString(stat))
477 gpu_info->n_dev = ndev;
478 gpu_info->gpu_dev = devs;
481 void get_gpu_device_info_string(char* s, const gmx_gpu_info_t& gpu_info, int index)
485 if (index < 0 && index >= gpu_info.n_dev)
490 gmx_device_info_t* dinfo = &gpu_info.gpu_dev[index];
492 bool bGpuExists = (dinfo->stat != egpuNonexistent && dinfo->stat != egpuInsane);
496 sprintf(s, "#%d: %s, stat: %s", dinfo->id, "N/A", gpu_detect_res_str[dinfo->stat]);
500 sprintf(s, "#%d: NVIDIA %s, compute cap.: %d.%d, ECC: %3s, stat: %s", dinfo->id,
501 dinfo->prop.name, dinfo->prop.major, dinfo->prop.minor,
502 dinfo->prop.ECCEnabled ? "yes" : " no", gpu_detect_res_str[dinfo->stat]);
506 int get_current_cuda_gpu_device_id(void)
509 CU_RET_ERR(cudaGetDevice(&gpuid), "cudaGetDevice failed");
514 size_t sizeof_gpu_dev_info(void)
516 return sizeof(gmx_device_info_t);
519 void startGpuProfiler(void)
521 /* The NVPROF_ID environment variable is set by nvprof and indicates that
522 mdrun is executed in the CUDA profiler.
523 If nvprof was run is with "--profile-from-start off", the profiler will
524 be started here. This way we can avoid tracing the CUDA events from the
525 first part of the run. Starting the profiler again does nothing.
530 stat = cudaProfilerStart();
531 CU_RET_ERR(stat, "cudaProfilerStart failed");
535 void stopGpuProfiler(void)
537 /* Stopping the nvidia here allows us to eliminate the subsequent
538 API calls from the trace, e.g. uninitialization and cleanup. */
542 stat = cudaProfilerStop();
543 CU_RET_ERR(stat, "cudaProfilerStop failed");
547 void resetGpuProfiler(void)
549 /* With CUDA <=7.5 the profiler can't be properly reset; we can only start
550 * the profiling here (can't stop it) which will achieve the desired effect if
551 * the run was started with the profiling disabled.
553 * TODO: add a stop (or replace it with reset) when this will work correctly in CUDA.
562 int gpu_info_get_stat(const gmx_gpu_info_t& info, int index)
564 return info.gpu_dev[index].stat;
567 /*! \brief Check status returned from peer access CUDA call, and error out or warn appropriately
568 * \param[in] stat CUDA call return status
569 * \param[in] gpuA ID for GPU initiating peer access call
570 * \param[in] gpuB ID for remote GPU
571 * \param[in] mdlog Logger object
572 * \param[in] cudaCallName name of CUDA peer access call
574 static void peerAccessCheckStat(const cudaError_t stat,
577 const gmx::MDLogger& mdlog,
578 const char* cudaCallName)
580 if ((stat == cudaErrorInvalidDevice) || (stat == cudaErrorInvalidValue))
582 std::string errorString =
583 gmx::formatString("%s from GPU %d to GPU %d failed", cudaCallName, gpuA, gpuB);
584 CU_RET_ERR(stat, errorString.c_str());
586 if (stat != cudaSuccess)
588 GMX_LOG(mdlog.warning)
590 .appendTextFormatted(
591 "GPU peer access not enabled between GPUs %d and %d due to unexpected "
592 "return value from %s: %s",
593 gpuA, gpuB, cudaCallName, cudaGetErrorString(stat));
597 void setupGpuDevicePeerAccess(const std::vector<int>& gpuIdsToUse, const gmx::MDLogger& mdlog)
601 // take a note of currently-set GPU
603 stat = cudaGetDevice(¤tGpu);
604 CU_RET_ERR(stat, "cudaGetDevice in setupGpuDevicePeerAccess failed");
606 std::string message = gmx::formatString(
607 "Note: Peer access enabled between the following GPU pairs in the node:\n ");
608 bool peerAccessEnabled = false;
610 for (unsigned int i = 0; i < gpuIdsToUse.size(); i++)
612 int gpuA = gpuIdsToUse[i];
613 stat = cudaSetDevice(gpuA);
614 if (stat != cudaSuccess)
616 GMX_LOG(mdlog.warning)
618 .appendTextFormatted(
619 "GPU peer access not enabled due to unexpected return value from "
620 "cudaSetDevice(%d): %s",
621 gpuA, cudaGetErrorString(stat));
624 for (unsigned int j = 0; j < gpuIdsToUse.size(); j++)
628 int gpuB = gpuIdsToUse[j];
629 int canAccessPeer = 0;
630 stat = cudaDeviceCanAccessPeer(&canAccessPeer, gpuA, gpuB);
631 peerAccessCheckStat(stat, gpuA, gpuB, mdlog, "cudaDeviceCanAccessPeer");
635 stat = cudaDeviceEnablePeerAccess(gpuB, 0);
636 peerAccessCheckStat(stat, gpuA, gpuB, mdlog, "cudaDeviceEnablePeerAccess");
638 message = gmx::formatString("%s%d->%d ", message.c_str(), gpuA, gpuB);
639 peerAccessEnabled = true;
645 // re-set GPU to that originally set
646 stat = cudaSetDevice(currentGpu);
647 if (stat != cudaSuccess)
649 CU_RET_ERR(stat, "cudaSetDevice in setupGpuDevicePeerAccess failed");
653 if (peerAccessEnabled)
655 GMX_LOG(mdlog.info).asParagraph().appendTextFormatted("%s", message.c_str());