Refactor sysconf checking
[alexxy/gromacs.git] / src / gromacs / gpu_utils / gpu_utils.cu
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2010,2011,2012,2013,2014,2015,2016, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \file
36  *  \brief Define functions for detection and initialization for CUDA devices.
37  *
38  *  \author Szilard Pall <pall.szilard@gmail.com>
39  */
40
41 #include "gmxpre.h"
42
43 #include "gpu_utils.h"
44
45 #include "config.h"
46
47 #include <assert.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50
51 #include <cuda_profiler_api.h>
52
53 #include "gromacs/gpu_utils/cudautils.cuh"
54 #include "gromacs/gpu_utils/pmalloc_cuda.h"
55 #include "gromacs/hardware/gpu_hw_info.h"
56 #include "gromacs/utility/basedefinitions.h"
57 #include "gromacs/utility/cstringutil.h"
58 #include "gromacs/utility/smalloc.h"
59
60 #if HAVE_NVML
61 #include <nvml.h>
62 #define HAVE_NVML_APPLICATION_CLOCKS (NVML_API_VERSION >= 6)
63 #else  /* HAVE_NVML */
64 #define HAVE_NVML_APPLICATION_CLOCKS 0
65 #endif /* HAVE_NVML */
66
67 #if defined(CHECK_CUDA_ERRORS) && HAVE_NVML_APPLICATION_CLOCKS
68 /*! Check for NVML error on the return status of a NVML API call. */
69 #  define HANDLE_NVML_RET_ERR(status, msg) \
70     do { \
71         if (status != NVML_SUCCESS) \
72         { \
73             gmx_warning("%s: %s\n", msg, nvmlErrorString(status)); \
74         } \
75     } while (0)
76 #else  /* defined(CHECK_CUDA_ERRORS) && HAVE_NVML_APPLICATION_CLOCKS */
77 #  define HANDLE_NVML_RET_ERR(status, msg) do { } while (0)
78 #endif /* defined(CHECK_CUDA_ERRORS) && HAVE_NVML_APPLICATION_CLOCKS */
79
80 #if HAVE_NVML_APPLICATION_CLOCKS
81 static const gmx_bool            bCompiledWithApplicationClockSupport = true;
82 #else
83 static const gmx_bool gmx_unused bCompiledWithApplicationClockSupport = false;
84 #endif
85
86 /*! \internal \brief
87  * Max number of devices supported by CUDA (for consistency checking).
88  *
89  * In reality it is 16 with CUDA <=v5.0, but let's stay on the safe side.
90  */
91 static int  cuda_max_device_count = 32;
92
93 static bool cudaProfilerRun      = ((getenv("NVPROF_ID") != NULL));
94
95 /** Dummy kernel used for sanity checking. */
96 __global__ void k_dummy_test()
97 {
98 }
99
100
101 /*!
102  * \brief Runs GPU sanity checks.
103  *
104  * Runs a series of checks to determine that the given GPU and underlying CUDA
105  * driver/runtime functions properly.
106  * Returns properties of a device with given ID or the one that has
107  * already been initialized earlier in the case if of \dev_id == -1.
108  *
109  * \param[in]  dev_id      the device ID of the GPU or -1 if the device has already been initialized
110  * \param[out] dev_prop    pointer to the structure in which the device properties will be returned
111  * \returns                0 if the device looks OK
112  *
113  * TODO: introduce errors codes and handle errors more smoothly.
114  */
115 static int do_sanity_checks(int dev_id, cudaDeviceProp *dev_prop)
116 {
117     cudaError_t cu_err;
118     int         dev_count, id;
119
120     cu_err = cudaGetDeviceCount(&dev_count);
121     if (cu_err != cudaSuccess)
122     {
123         fprintf(stderr, "Error %d while querying device count: %s\n", cu_err,
124                 cudaGetErrorString(cu_err));
125         return -1;
126     }
127
128     /* no CUDA compatible device at all */
129     if (dev_count == 0)
130     {
131         return -1;
132     }
133
134     /* things might go horribly wrong if cudart is not compatible with the driver */
135     if (dev_count < 0 || dev_count > cuda_max_device_count)
136     {
137         return -1;
138     }
139
140     if (dev_id == -1) /* device already selected let's not destroy the context */
141     {
142         cu_err = cudaGetDevice(&id);
143         if (cu_err != cudaSuccess)
144         {
145             fprintf(stderr, "Error %d while querying device id: %s\n", cu_err,
146                     cudaGetErrorString(cu_err));
147             return -1;
148         }
149     }
150     else
151     {
152         id = dev_id;
153         if (id > dev_count - 1) /* pfff there's no such device */
154         {
155             fprintf(stderr, "The requested device with id %d does not seem to exist (device count=%d)\n",
156                     dev_id, dev_count);
157             return -1;
158         }
159     }
160
161     memset(dev_prop, 0, sizeof(cudaDeviceProp));
162     cu_err = cudaGetDeviceProperties(dev_prop, id);
163     if (cu_err != cudaSuccess)
164     {
165         fprintf(stderr, "Error %d while querying device properties: %s\n", cu_err,
166                 cudaGetErrorString(cu_err));
167         return -1;
168     }
169
170     /* both major & minor is 9999 if no CUDA capable devices are present */
171     if (dev_prop->major == 9999 && dev_prop->minor == 9999)
172     {
173         return -1;
174     }
175     /* we don't care about emulation mode */
176     if (dev_prop->major == 0)
177     {
178         return -1;
179     }
180
181     if (id != -1)
182     {
183         cu_err = cudaSetDevice(id);
184         if (cu_err != cudaSuccess)
185         {
186             fprintf(stderr, "Error %d while switching to device #%d: %s\n",
187                     cu_err, id, cudaGetErrorString(cu_err));
188             return -1;
189         }
190     }
191
192     /* try to execute a dummy kernel */
193     k_dummy_test<<< 1, 512>>> ();
194     if (cudaThreadSynchronize() != cudaSuccess)
195     {
196         return -1;
197     }
198
199     /* destroy context if we created one */
200     if (id != -1)
201     {
202         cu_err = cudaDeviceReset();
203         CU_RET_ERR(cu_err, "cudaDeviceReset failed");
204     }
205
206     return 0;
207 }
208
209 #if HAVE_NVML
210 /* TODO: We should actually be using md_print_warn in md_logging.c,
211  * but we can't include mpi.h in CUDA code.
212  */
213 static void md_print_info(FILE       *fplog,
214                           const char *fmt, ...)
215 {
216     va_list ap;
217
218     if (fplog != NULL)
219     {
220         /* We should only print to stderr on the master node,
221          * in most cases fplog is only set on the master node, so this works.
222          */
223         va_start(ap, fmt);
224         vfprintf(stderr, fmt, ap);
225         va_end(ap);
226
227         va_start(ap, fmt);
228         vfprintf(fplog, fmt, ap);
229         va_end(ap);
230     }
231 }
232 #endif /*HAVE_NVML*/
233
234 /* TODO: We should actually be using md_print_warn in md_logging.c,
235  * but we can't include mpi.h in CUDA code.
236  * This is replicated from nbnxn_cuda_data_mgmt.cu.
237  */
238 static void md_print_warn(FILE       *fplog,
239                           const char *fmt, ...)
240 {
241     va_list ap;
242
243     if (fplog != NULL)
244     {
245         /* We should only print to stderr on the master node,
246          * in most cases fplog is only set on the master node, so this works.
247          */
248         va_start(ap, fmt);
249         fprintf(stderr, "\n");
250         vfprintf(stderr, fmt, ap);
251         fprintf(stderr, "\n");
252         va_end(ap);
253
254         va_start(ap, fmt);
255         fprintf(fplog, "\n");
256         vfprintf(fplog, fmt, ap);
257         fprintf(fplog, "\n");
258         va_end(ap);
259     }
260 }
261
262 #if HAVE_NVML_APPLICATION_CLOCKS
263 /*! \brief Determines and adds the NVML device ID to the passed \cuda_dev.
264  *
265  * Determines and adds the NVML device ID to the passed \cuda_dev. This is done by
266  * matching PCI-E information from \cuda_dev with the available NVML devices.
267  *
268  * \param[in,out] cuda_dev  CUDA device information to enrich with NVML device info
269  * \returns                 true if \cuda_dev could be enriched with matching NVML device information.
270  */
271 static bool addNVMLDeviceId(gmx_device_info_t* cuda_dev)
272 {
273     nvmlDevice_t nvml_device_id;
274     unsigned int nvml_device_count = 0;
275     nvmlReturn_t nvml_stat         = nvmlDeviceGetCount ( &nvml_device_count );
276     cuda_dev->nvml_initialized = false;
277     HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetCount failed" );
278     for (unsigned int nvml_device_idx = 0; nvml_stat == NVML_SUCCESS && nvml_device_idx < nvml_device_count; ++nvml_device_idx)
279     {
280         nvml_stat = nvmlDeviceGetHandleByIndex ( nvml_device_idx, &nvml_device_id );
281         HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetHandleByIndex failed" );
282         if (nvml_stat != NVML_SUCCESS)
283         {
284             break;
285         }
286
287         nvmlPciInfo_t nvml_pci_info;
288         nvml_stat = nvmlDeviceGetPciInfo ( nvml_device_id, &nvml_pci_info );
289         HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetPciInfo failed" );
290         if (nvml_stat != NVML_SUCCESS)
291         {
292             break;
293         }
294         if (static_cast<unsigned int>(cuda_dev->prop.pciBusID) == nvml_pci_info.bus &&
295             static_cast<unsigned int>(cuda_dev->prop.pciDeviceID) == nvml_pci_info.device &&
296             static_cast<unsigned int>(cuda_dev->prop.pciDomainID) == nvml_pci_info.domain)
297         {
298             cuda_dev->nvml_initialized = true;
299             cuda_dev->nvml_device_id   = nvml_device_id;
300             break;
301         }
302     }
303     return cuda_dev->nvml_initialized;
304 }
305 #endif /* HAVE_NVML_APPLICATION_CLOCKS */
306
307 /*! \brief Tries to set application clocks for the GPU with the given index.
308  *
309  * The variable \gpuid is the index of the GPU in the gpu_info.cuda_dev array
310  * to handle the application clocks for. Application clocks are set to the
311  * max supported value to increase performance if application clock permissions
312  * allow this. For future GPU architectures a more sophisticated scheme might be
313  * required.
314  *
315  * \param[out] fplog        log file to write to
316  * \param[in] gpuid         index of the GPU to set application clocks for
317  * \param[in] gpu_info      GPU info of all detected devices in the system.
318  * \returns                 true if no error occurs during application clocks handling.
319  */
320 static gmx_bool init_gpu_application_clocks(FILE gmx_unused *fplog, int gmx_unused gpuid, const gmx_gpu_info_t gmx_unused *gpu_info)
321 {
322     const cudaDeviceProp *prop                        = &gpu_info->gpu_dev[gpuid].prop;
323     int                   cuda_version_number         = prop->major * 10 + prop->minor;
324     gmx_bool              bGpuCanUseApplicationClocks =
325         ((0 == gmx_wcmatch("*Tesla*", prop->name) && cuda_version_number >= 35 ) ||
326          (0 == gmx_wcmatch("*Quadro*", prop->name) && cuda_version_number >= 52 ));
327     if (!bGpuCanUseApplicationClocks)
328     {
329         return true;
330     }
331 #if !HAVE_NVML
332     int cuda_driver  = 0;
333     int cuda_runtime = 0;
334     cudaDriverGetVersion(&cuda_driver);
335     cudaRuntimeGetVersion(&cuda_runtime);
336     md_print_warn( fplog, "NOTE: GROMACS was configured without NVML support hence it can not exploit\n"
337                    "      application clocks of the detected %s GPU to improve performance.\n"
338                    "      Recompile with the NVML library (compatible with the driver used) or set application clocks manually.\n",
339                    prop->name);
340     return true;
341 #else
342     if (!bCompiledWithApplicationClockSupport)
343     {
344         int cuda_driver  = 0;
345         int cuda_runtime = 0;
346         cudaDriverGetVersion(&cuda_driver);
347         cudaRuntimeGetVersion(&cuda_runtime);
348         md_print_warn( fplog, "NOTE: GROMACS was compiled with an old NVML library which does not support\n"
349                        "      managing application clocks of the detected %s GPU to improve performance.\n"
350                        "      If your GPU supports application clocks, upgrade NVML (and driver) and recompile or set the clocks manually.\n",
351                        prop->name );
352         return true;
353     }
354
355     /* We've compiled with NVML application clocks support, and have a GPU that can use it */
356     nvmlReturn_t nvml_stat = NVML_SUCCESS;
357     char        *env;
358     //TODO: GMX_GPU_APPLICATION_CLOCKS is currently only used to enable/disable setting of application clocks
359     //      this variable can be later used to give a user more fine grained control.
360     env = getenv("GMX_GPU_APPLICATION_CLOCKS");
361     if (env != NULL && ( strcmp( env, "0") == 0 ||
362                          gmx_strcasecmp( env, "OFF") == 0 ||
363                          gmx_strcasecmp( env, "DISABLE") == 0 ))
364     {
365         return true;
366     }
367     nvml_stat = nvmlInit();
368     HANDLE_NVML_RET_ERR( nvml_stat, "nvmlInit failed." );
369     if (nvml_stat != NVML_SUCCESS)
370     {
371         return false;
372     }
373     if (!addNVMLDeviceId( &(gpu_info->gpu_dev[gpuid])))
374     {
375         return false;
376     }
377     //get current application clocks setting
378     unsigned int app_sm_clock  = 0;
379     unsigned int app_mem_clock = 0;
380     nvml_stat = nvmlDeviceGetApplicationsClock ( gpu_info->gpu_dev[gpuid].nvml_device_id, NVML_CLOCK_SM, &app_sm_clock );
381     if (NVML_ERROR_NOT_SUPPORTED == nvml_stat)
382     {
383         return false;
384     }
385     HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetApplicationsClock failed" );
386     nvml_stat = nvmlDeviceGetApplicationsClock ( gpu_info->gpu_dev[gpuid].nvml_device_id, NVML_CLOCK_MEM, &app_mem_clock );
387     HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetApplicationsClock failed" );
388     //get max application clocks
389     unsigned int max_sm_clock  = 0;
390     unsigned int max_mem_clock = 0;
391     nvml_stat = nvmlDeviceGetMaxClockInfo ( gpu_info->gpu_dev[gpuid].nvml_device_id, NVML_CLOCK_SM, &max_sm_clock );
392     HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetMaxClockInfo failed" );
393     nvml_stat = nvmlDeviceGetMaxClockInfo ( gpu_info->gpu_dev[gpuid].nvml_device_id, NVML_CLOCK_MEM, &max_mem_clock );
394     HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetMaxClockInfo failed" );
395
396     gpu_info->gpu_dev[gpuid].nvml_is_restricted     = NVML_FEATURE_ENABLED;
397     gpu_info->gpu_dev[gpuid].nvml_ap_clocks_changed = false;
398
399     nvml_stat = nvmlDeviceGetAPIRestriction ( gpu_info->gpu_dev[gpuid].nvml_device_id, NVML_RESTRICTED_API_SET_APPLICATION_CLOCKS, &(gpu_info->gpu_dev[gpuid].nvml_is_restricted) );
400     HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetAPIRestriction failed" );
401
402     /* Note: Distinguishing between different types of GPUs here might be necessary in the future,
403        e.g. if max application clocks should not be used for certain GPUs. */
404     if (nvml_stat == NVML_SUCCESS && app_sm_clock < max_sm_clock && gpu_info->gpu_dev[gpuid].nvml_is_restricted == NVML_FEATURE_DISABLED)
405     {
406         md_print_info( fplog, "Changing GPU application clocks for %s to (%d,%d)\n", gpu_info->gpu_dev[gpuid].prop.name, max_mem_clock, max_sm_clock);
407         nvml_stat = nvmlDeviceSetApplicationsClocks ( gpu_info->gpu_dev[gpuid].nvml_device_id, max_mem_clock, max_sm_clock );
408         HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceGetApplicationsClock failed" );
409         gpu_info->gpu_dev[gpuid].nvml_ap_clocks_changed = true;
410     }
411     else if (nvml_stat == NVML_SUCCESS && app_sm_clock < max_sm_clock)
412     {
413         md_print_warn( fplog, "Can not change application clocks for %s to optimal values due to insufficient permissions. Current values are (%d,%d), max values are (%d,%d).\nUse sudo nvidia-smi -acp UNRESTRICTED or contact your admin to change application clocks.\n", gpu_info->gpu_dev[gpuid].prop.name, app_mem_clock, app_sm_clock, max_mem_clock, max_sm_clock);
414     }
415     else if (nvml_stat == NVML_SUCCESS && app_sm_clock == max_sm_clock)
416     {
417         //TODO: This should probably be integrated into the GPU Properties table.
418         md_print_info( fplog, "Application clocks (GPU clocks) for %s are (%d,%d)\n", gpu_info->gpu_dev[gpuid].prop.name, app_mem_clock, app_sm_clock);
419     }
420     else
421     {
422         md_print_warn( fplog,  "Can not change GPU application clocks to optimal values due to NVML error (%d): %s.\n", nvml_stat, nvmlErrorString(nvml_stat));
423     }
424     return (nvml_stat == NVML_SUCCESS);
425 #endif /* HAVE_NVML */
426 }
427
428 /*! \brief Resets application clocks if changed and cleans up NVML for the passed \gpu_dev.
429  *
430  * \param[in] gpu_dev  CUDA device information
431  */
432 static gmx_bool reset_gpu_application_clocks(const gmx_device_info_t gmx_unused * cuda_dev)
433 {
434 #if !HAVE_NVML_APPLICATION_CLOCKS
435     GMX_UNUSED_VALUE(cuda_dev);
436     return true;
437 #else /* HAVE_NVML_APPLICATION_CLOCKS */
438     nvmlReturn_t nvml_stat = NVML_SUCCESS;
439     if (cuda_dev &&
440         cuda_dev->nvml_is_restricted == NVML_FEATURE_DISABLED &&
441         cuda_dev->nvml_ap_clocks_changed)
442     {
443         nvml_stat = nvmlDeviceResetApplicationsClocks( cuda_dev->nvml_device_id );
444         HANDLE_NVML_RET_ERR( nvml_stat, "nvmlDeviceResetApplicationsClocks failed" );
445     }
446     nvml_stat = nvmlShutdown();
447     HANDLE_NVML_RET_ERR( nvml_stat, "nvmlShutdown failed" );
448     return (nvml_stat == NVML_SUCCESS);
449 #endif /* HAVE_NVML_APPLICATION_CLOCKS */
450 }
451
452 gmx_bool init_gpu(FILE gmx_unused *fplog, int mygpu, char *result_str,
453                   const struct gmx_gpu_info_t *gpu_info,
454                   const struct gmx_gpu_opt_t *gpu_opt)
455 {
456     cudaError_t stat;
457     char        sbuf[STRLEN];
458     int         gpuid;
459
460     assert(gpu_info);
461     assert(result_str);
462
463     if (mygpu < 0 || mygpu >= gpu_opt->n_dev_use)
464     {
465         sprintf(sbuf, "Trying to initialize an inexistent GPU: "
466                 "there are %d %s-selected GPU(s), but #%d was requested.",
467                 gpu_opt->n_dev_use, gpu_opt->bUserSet ? "user" : "auto", mygpu);
468         gmx_incons(sbuf);
469     }
470
471     gpuid = gpu_info->gpu_dev[gpu_opt->dev_use[mygpu]].id;
472
473     stat = cudaSetDevice(gpuid);
474     strncpy(result_str, cudaGetErrorString(stat), STRLEN);
475
476     if (debug)
477     {
478         fprintf(stderr, "Initialized GPU ID #%d: %s\n", gpuid, gpu_info->gpu_dev[gpuid].prop.name);
479     }
480
481     //Ignoring return value as NVML errors should be treated not critical.
482     if (stat == cudaSuccess)
483     {
484         init_gpu_application_clocks(fplog, gpuid, gpu_info);
485     }
486     return (stat == cudaSuccess);
487 }
488
489 gmx_bool free_cuda_gpu(
490         int gmx_unused mygpu, char *result_str,
491         const gmx_gpu_info_t gmx_unused *gpu_info,
492         const gmx_gpu_opt_t gmx_unused *gpu_opt
493         )
494 {
495     cudaError_t  stat;
496     gmx_bool     reset_gpu_application_clocks_status = true;
497     int          gpuid;
498
499     assert(result_str);
500
501     if (debug)
502     {
503         int gpuid;
504         stat = cudaGetDevice(&gpuid);
505         CU_RET_ERR(stat, "cudaGetDevice failed");
506         fprintf(stderr, "Cleaning up context on GPU ID #%d\n", gpuid);
507     }
508
509     gpuid = gpu_opt ? gpu_opt->dev_use[mygpu] : -1;
510     if (gpuid != -1)
511     {
512         reset_gpu_application_clocks_status = reset_gpu_application_clocks( &(gpu_info->gpu_dev[gpuid]) );
513     }
514
515     stat = cudaDeviceReset();
516     strncpy(result_str, cudaGetErrorString(stat), STRLEN);
517     return (stat == cudaSuccess) && reset_gpu_application_clocks_status;
518 }
519
520 /*! \brief Returns true if the gpu characterized by the device properties is
521  *  supported by the native gpu acceleration.
522  *
523  * \param[in] dev_prop  the CUDA device properties of the gpus to test.
524  * \returns             true if the GPU properties passed indicate a compatible
525  *                      GPU, otherwise false.
526  */
527 static bool is_gmx_supported_gpu(const cudaDeviceProp *dev_prop)
528 {
529     return (dev_prop->major >= 2);
530 }
531
532 /*! \brief Helper function that checks whether a given GPU status indicates compatible GPU.
533  *
534  * \param[in] stat  GPU status.
535  * \returns         true if the provided status is egpuCompatible, otherwise false.
536  */
537 static bool is_compatible_gpu(int stat)
538 {
539     return (stat == egpuCompatible);
540 }
541
542 /*! \brief Checks if a GPU with a given ID is supported by the native GROMACS acceleration.
543  *
544  *  Returns a status value which indicates compatibility or one of the following
545  *  errors: incompatibility, insistence, or insanity (=unexpected behavior).
546  *  It also returns the respective device's properties in \dev_prop (if applicable).
547  *
548  *  \param[in]  dev_id   the ID of the GPU to check.
549  *  \param[out] dev_prop the CUDA device properties of the device checked.
550  *  \returns             the status of the requested device
551  */
552 static int is_gmx_supported_gpu_id(int dev_id, cudaDeviceProp *dev_prop)
553 {
554     cudaError_t stat;
555     int         ndev;
556
557     stat = cudaGetDeviceCount(&ndev);
558     if (stat != cudaSuccess)
559     {
560         return egpuInsane;
561     }
562
563     if (dev_id > ndev - 1)
564     {
565         return egpuNonexistent;
566     }
567
568     /* TODO: currently we do not make a distinction between the type of errors
569      * that can appear during sanity checks. This needs to be improved, e.g if
570      * the dummy test kernel fails to execute with a "device busy message" we
571      * should appropriately report that the device is busy instead of insane.
572      */
573     if (do_sanity_checks(dev_id, dev_prop) == 0)
574     {
575         if (is_gmx_supported_gpu(dev_prop))
576         {
577             return egpuCompatible;
578         }
579         else
580         {
581             return egpuIncompatible;
582         }
583     }
584     else
585     {
586         return egpuInsane;
587     }
588 }
589
590
591 int detect_gpus(gmx_gpu_info_t *gpu_info, char *err_str)
592 {
593     int                i, ndev, checkres, retval;
594     cudaError_t        stat;
595     cudaDeviceProp     prop;
596     gmx_device_info_t *devs;
597
598     assert(gpu_info);
599     assert(err_str);
600
601     gpu_info->n_dev_compatible = 0;
602
603     ndev    = 0;
604     devs    = NULL;
605
606     stat = cudaGetDeviceCount(&ndev);
607     if (stat != cudaSuccess)
608     {
609         const char *s;
610
611         /* cudaGetDeviceCount failed which means that there is something
612          * wrong with the machine: driver-runtime mismatch, all GPUs being
613          * busy in exclusive mode, or some other condition which should
614          * result in us issuing a warning a falling back to CPUs. */
615         retval = -1;
616         s      = cudaGetErrorString(stat);
617         strncpy(err_str, s, STRLEN*sizeof(err_str[0]));
618     }
619     else
620     {
621         snew(devs, ndev);
622         for (i = 0; i < ndev; i++)
623         {
624             checkres = is_gmx_supported_gpu_id(i, &prop);
625
626             devs[i].id   = i;
627             devs[i].prop = prop;
628             devs[i].stat = checkres;
629
630             if (checkres == egpuCompatible)
631             {
632                 gpu_info->n_dev_compatible++;
633             }
634         }
635         retval = 0;
636     }
637
638     gpu_info->n_dev   = ndev;
639     gpu_info->gpu_dev = devs;
640
641     return retval;
642 }
643
644 void pick_compatible_gpus(const gmx_gpu_info_t *gpu_info,
645                           gmx_gpu_opt_t        *gpu_opt)
646 {
647     int  i, ncompat;
648     int *compat;
649
650     assert(gpu_info);
651     /* gpu_dev/n_dev have to be either NULL/0 or not (NULL/0) */
652     assert((gpu_info->n_dev != 0 ? 0 : 1) ^ (gpu_info->gpu_dev == NULL ? 0 : 1));
653
654     snew(compat, gpu_info->n_dev);
655     ncompat = 0;
656     for (i = 0; i < gpu_info->n_dev; i++)
657     {
658         if (is_compatible_gpu(gpu_info->gpu_dev[i].stat))
659         {
660             ncompat++;
661             compat[ncompat - 1] = i;
662         }
663     }
664
665     gpu_opt->n_dev_compatible = ncompat;
666     snew(gpu_opt->dev_compatible, ncompat);
667     memcpy(gpu_opt->dev_compatible, compat, ncompat*sizeof(*compat));
668     sfree(compat);
669 }
670
671 gmx_bool check_selected_gpus(int                  *checkres,
672                              const gmx_gpu_info_t *gpu_info,
673                              gmx_gpu_opt_t        *gpu_opt)
674 {
675     int  i, id;
676     bool bAllOk;
677
678     assert(checkres);
679     assert(gpu_info);
680     assert(gpu_opt->n_dev_use >= 0);
681
682     if (gpu_opt->n_dev_use == 0)
683     {
684         return TRUE;
685     }
686
687     assert(gpu_opt->dev_use);
688
689     /* we will assume that all GPUs requested are valid IDs,
690        otherwise we'll bail anyways */
691
692     bAllOk = true;
693     for (i = 0; i < gpu_opt->n_dev_use; i++)
694     {
695         id = gpu_opt->dev_use[i];
696
697         /* devices are stored in increasing order of IDs in gpu_dev */
698         gpu_opt->dev_use[i] = id;
699
700         checkres[i] = (id >= gpu_info->n_dev) ?
701             egpuNonexistent : gpu_info->gpu_dev[id].stat;
702
703         bAllOk = bAllOk && is_compatible_gpu(checkres[i]);
704     }
705
706     return bAllOk;
707 }
708
709 void free_gpu_info(const gmx_gpu_info_t *gpu_info)
710 {
711     if (gpu_info == NULL)
712     {
713         return;
714     }
715
716     sfree(gpu_info->gpu_dev);
717 }
718
719 void get_gpu_device_info_string(char *s, const gmx_gpu_info_t *gpu_info, int index)
720 {
721     assert(s);
722     assert(gpu_info);
723
724     if (index < 0 && index >= gpu_info->n_dev)
725     {
726         return;
727     }
728
729     gmx_device_info_t *dinfo = &gpu_info->gpu_dev[index];
730
731     bool               bGpuExists =
732         dinfo->stat == egpuCompatible ||
733         dinfo->stat == egpuIncompatible;
734
735     if (!bGpuExists)
736     {
737         sprintf(s, "#%d: %s, stat: %s",
738                 dinfo->id, "N/A",
739                 gpu_detect_res_str[dinfo->stat]);
740     }
741     else
742     {
743         sprintf(s, "#%d: NVIDIA %s, compute cap.: %d.%d, ECC: %3s, stat: %s",
744                 dinfo->id, dinfo->prop.name,
745                 dinfo->prop.major, dinfo->prop.minor,
746                 dinfo->prop.ECCEnabled ? "yes" : " no",
747                 gpu_detect_res_str[dinfo->stat]);
748     }
749 }
750
751 int get_gpu_device_id(const gmx_gpu_info_t *gpu_info,
752                       const gmx_gpu_opt_t  *gpu_opt,
753                       int                   idx)
754 {
755     assert(gpu_info);
756     assert(gpu_opt);
757     assert(idx >= 0 && idx < gpu_opt->n_dev_use);
758
759     return gpu_info->gpu_dev[gpu_opt->dev_use[idx]].id;
760 }
761
762 int get_current_cuda_gpu_device_id(void)
763 {
764     int gpuid;
765     CU_RET_ERR(cudaGetDevice(&gpuid), "cudaGetDevice failed");
766
767     return gpuid;
768 }
769
770 size_t sizeof_gpu_dev_info(void)
771 {
772     return sizeof(gmx_device_info_t);
773 }
774
775 void gpu_set_host_malloc_and_free(bool               bUseGpuKernels,
776                                   gmx_host_alloc_t **nb_alloc,
777                                   gmx_host_free_t  **nb_free)
778 {
779     if (bUseGpuKernels)
780     {
781         *nb_alloc = &pmalloc;
782         *nb_free  = &pfree;
783     }
784     else
785     {
786         *nb_alloc = NULL;
787         *nb_free  = NULL;
788     }
789 }
790
791 void startGpuProfiler(void)
792 {
793     /* The NVPROF_ID environment variable is set by nvprof and indicates that
794        mdrun is executed in the CUDA profiler.
795        If nvprof was run is with "--profile-from-start off", the profiler will
796        be started here. This way we can avoid tracing the CUDA events from the
797        first part of the run. Starting the profiler again does nothing.
798      */
799     if (cudaProfilerRun)
800     {
801         cudaError_t stat;
802         stat = cudaProfilerStart();
803         CU_RET_ERR(stat, "cudaProfilerStart failed");
804     }
805 }
806
807 void stopGpuProfiler(void)
808 {
809     /* Stopping the nvidia here allows us to eliminate the subsequent
810        API calls from the trace, e.g. uninitialization and cleanup. */
811     if (cudaProfilerRun)
812     {
813         cudaError_t stat;
814         stat = cudaProfilerStop();
815         CU_RET_ERR(stat, "cudaProfilerStop failed");
816     }
817 }
818
819 void resetGpuProfiler(void)
820 {
821     /* With CUDA <=7.5 the profiler can't be properly reset; we can only start
822      *  the profiling here (can't stop it) which will achieve the desired effect if
823      *  the run was started with the profiling disabled.
824      *
825      * TODO: add a stop (or replace it with reset) when this will work correctly in CUDA.
826      * stopGpuProfiler();
827      */
828     if (cudaProfilerRun)
829     {
830         startGpuProfiler();
831     }
832 }