Merge release-4-6 into master
[alexxy/gromacs.git] / src / gromacs / gmxlib / gmx_detect_hardware.c
1 /* -*- mode: c; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; c-file-style: "stroustrup"; -*-
2  *
3  * 
4  * This file is part of GROMACS.
5  * Copyright (c) 2012-  
6  *
7  * Written by the Gromacs development team under coordination of
8  * David van der Spoel, Berk Hess, and Erik Lindahl.
9  *
10  * This library 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
13  * of the License, or (at your option) any later version.
14  *
15  * To help us fund GROMACS development, we humbly ask that you cite
16  * the research papers on the package. Check out http://www.gromacs.org
17  * 
18  * And Hey:
19  * GROup of MAchos and Cynical Suckers
20  */
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <stdlib.h>
26 #include <assert.h>
27 #include <string.h>
28
29 #include "types/enums.h"
30 #include "types/hw_info.h"
31 #include "types/commrec.h"
32 #include "gmx_fatal.h"
33 #include "gmx_fatal_collective.h"
34 #include "smalloc.h"
35 #include "gpu_utils.h"
36 #include "statutil.h"
37 #include "gmx_detect_hardware.h"
38 #include "main.h"
39 #include "md_logging.h"
40
41 #if ((defined(WIN32) || defined( _WIN32 ) || defined(WIN64) || defined( _WIN64 )) && !(defined (__CYGWIN__) || defined (__CYGWIN32__)))
42 #include "windows.h"
43 #endif
44
45 /* Although we can't have more than 10 GPU different ID-s passed by the user as
46  * the id-s are assumed to be represented by single digits, as multiple
47  * processes can share a GPU, we can end up with more than 10 IDs.
48  * To account for potential extreme cases we'll set the limit to a pretty
49  * ridiculous number. */
50 static unsigned int max_gpu_ids_user = 64;
51
52 static const char* invalid_gpuid_hint =
53     "A delimiter-free sequence of valid numeric IDs of available GPUs is expected.";
54
55 /* FW decl. */
56 void limit_num_gpus_used(gmx_hw_info_t *hwinfo, int count);
57
58 static void sprint_gpus(char *sbuf, const gmx_gpu_info_t *gpu_info, gmx_bool bPrintAll)
59 {
60     int      i, ndev;
61     char     stmp[STRLEN];
62
63     ndev = gpu_info->ncuda_dev;
64
65     sbuf[0] = '\0';
66     for (i = 0; i < ndev; i++)
67     {
68         get_gpu_device_info_string(stmp, gpu_info, i);
69         strcat(sbuf, "  ");
70         strcat(sbuf, stmp);
71         if (i < ndev - 1)
72         {
73             strcat(sbuf, "\n");
74         }
75     }
76 }
77
78 static void print_gpu_detection_stats(FILE *fplog,
79                                       const gmx_gpu_info_t *gpu_info,
80                                       const t_commrec *cr)
81 {
82     char onhost[266],stmp[STRLEN];
83     int  ngpu;
84
85     ngpu = gpu_info->ncuda_dev;
86
87 #if defined GMX_MPI && !defined GMX_THREAD_MPI
88     /* We only print the detection on one, of possibly multiple, nodes */
89     strncpy(onhost," on host ",10);
90     gmx_gethostname(onhost+9,256);
91 #else
92     /* We detect all relevant GPUs */
93     strncpy(onhost,"",1);
94 #endif
95
96     if (ngpu > 0)
97     {
98         sprint_gpus(stmp, gpu_info, TRUE);
99         md_print_warn(cr, fplog, "%d GPU%s detected%s:\n%s\n",
100                       ngpu, (ngpu > 1) ? "s" : "", onhost, stmp);
101     }
102     else
103     {
104         md_print_warn(cr, fplog, "No GPUs detected%s\n", onhost);
105     }
106 }
107
108 static void print_gpu_use_stats(FILE *fplog,
109                                 const gmx_gpu_info_t *gpu_info,
110                                 const t_commrec *cr)
111 {
112     char sbuf[STRLEN], stmp[STRLEN];
113     int  i, ngpu, ngpu_all;
114
115     ngpu     = gpu_info->ncuda_dev_use;
116     ngpu_all = gpu_info->ncuda_dev;
117
118     /* Issue note if GPUs are available but not used */
119     if (ngpu_all > 0 && ngpu < 1)
120     {
121         sprintf(sbuf,
122                 "%d compatible GPU%s detected in the system, but none will be used.\n"
123                 "Consider trying GPU acceleration with the Verlet scheme!",
124                 ngpu_all, (ngpu_all > 1) ? "s" : "");
125     }
126     else
127     {
128         sprintf(sbuf, "%d GPU%s %sselected for this run: ",
129                 ngpu, (ngpu > 1) ? "s" : "",
130                 gpu_info->bUserSet ? "user-" : "auto-");
131         for (i = 0; i < ngpu; i++)
132         {
133             sprintf(stmp, "#%d", get_gpu_device_id(gpu_info, i));
134             if (i < ngpu - 1)
135             {
136                 strcat(stmp, ", ");
137             }
138             strcat(sbuf, stmp);
139         }
140     }
141     md_print_info(cr, fplog, "%s\n\n", sbuf);
142 }
143
144 /* Parse a "plain" GPU ID string which contains a sequence of digits corresponding
145  * to GPU IDs; the order will indicate the process/tMPI thread - GPU assignment. */
146 static void parse_gpu_id_plain_string(const char *idstr, int *nid, int *idlist)
147 {
148     int  i;
149     size_t len_idstr;
150
151     len_idstr = strlen(idstr);
152
153     if (len_idstr > max_gpu_ids_user)
154     {
155         gmx_fatal(FARGS,"%d GPU IDs provided, but only at most %d are supported",
156                   len_idstr, max_gpu_ids_user);
157     }
158
159     *nid = len_idstr;
160
161     for (i = 0; i < *nid; i++)
162     {
163         if (idstr[i] < '0' || idstr[i] > '9')
164         {
165             gmx_fatal(FARGS, "Invalid character in GPU ID string: '%c'\n%s\n",
166                       idstr[i], invalid_gpuid_hint);
167         }
168         idlist[i] = idstr[i] - '0';
169     }
170 }
171
172 static void parse_gpu_id_csv_string(const char *idstr, int *nid, int *idlist)
173 {
174     /* XXX implement cvs format to support more than 10 different GPUs in a box. */
175     gmx_incons("Not implemented yet");
176 }
177
178 void gmx_check_hw_runconf_consistency(FILE *fplog, gmx_hw_info_t *hwinfo,
179                                       const t_commrec *cr, int ntmpi_requested,
180                                       gmx_bool bUseGPU)
181 {
182     int      npppn, ntmpi_pp, ngpu;
183     char     sbuf[STRLEN], th_or_proc[STRLEN], th_or_proc_plural[STRLEN], pernode[STRLEN];
184     char     gpu_plural[2];
185     gmx_bool bGPUBin, btMPI, bMPI, bMaxMpiThreadsSet, bNthreadsAuto, bEmulateGPU;
186
187     assert(hwinfo);
188     assert(cr);
189
190     btMPI = bMPI = FALSE;
191     bNthreadsAuto = FALSE;
192 #if defined(GMX_THREAD_MPI)
193     btMPI = TRUE;
194     bNthreadsAuto = (ntmpi_requested < 1);
195 #elif defined(GMX_LIB_MPI)
196     bMPI  = TRUE;
197 #endif
198
199 #ifdef GMX_GPU
200     bGPUBin      = TRUE;
201 #else
202     bGPUBin      = FALSE;
203 #endif
204
205     /* GPU emulation detection is done later, but we need here as well
206      * -- uncool, but there's no elegant workaround */
207     bEmulateGPU       = (getenv("GMX_EMULATE_GPU") != NULL);
208     bMaxMpiThreadsSet = (getenv("GMX_MAX_MPI_THREADS") != NULL);
209
210     if (SIMMASTER(cr))
211     {
212         /* check the acceleration mdrun is compiled with against hardware capabilities */
213         /* TODO: Here we assume homogeneous hardware which is not necessarily the case!
214          *       Might not hurt to add an extra check over MPI. */
215         gmx_cpuid_acceleration_check(hwinfo->cpuid_info, fplog);
216     }
217
218     /* Below we only do consistency checks for PP and GPUs,
219      * this is irrelevant for PME only nodes, so in that case we return here.
220      */
221     if (!(cr->duty & DUTY_PP))
222     {
223         return;
224     }
225
226     /* Need to ensure that we have enough GPUs:
227      * - need one GPU per PP node
228      * - no GPU oversubscription with tMPI
229      * => keep on the GPU support, otherwise turn off (or bail if forced)
230      * */
231     /* number of PP processes per node */
232     npppn = cr->nrank_pp_intranode;
233
234     pernode[0] = '\0';
235     th_or_proc_plural[0] = '\0';
236     if (btMPI)
237     {
238         sprintf(th_or_proc, "thread-MPI thread");
239         if (npppn > 1)
240         {
241             sprintf(th_or_proc_plural, "s");
242         }
243     }
244     else if (bMPI)
245     {
246         sprintf(th_or_proc, "MPI process");
247         if (npppn > 1)
248         {
249             sprintf(th_or_proc_plural, "es");
250         }
251         sprintf(pernode, " per node");
252     }
253     else
254     {
255         /* neither MPI nor tMPI */
256         sprintf(th_or_proc, "process");
257     }
258
259     if (bGPUBin)
260     {
261         print_gpu_detection_stats(fplog, &hwinfo->gpu_info, cr);
262     }
263
264     if (bUseGPU && hwinfo->bCanUseGPU && !bEmulateGPU)
265     {
266         ngpu = hwinfo->gpu_info.ncuda_dev_use;
267         sprintf(gpu_plural, "%s", (ngpu > 1) ? "s" : "");
268
269         /* number of tMPI threads atuo-adjusted */
270         if (btMPI && bNthreadsAuto && SIMMASTER(cr))
271         {
272             if (npppn < ngpu)
273             {
274                 if (hwinfo->gpu_info.bUserSet)
275                 {
276                     /* The user manually provided more GPUs than threads we could
277                      * automatically start. */
278                     gmx_fatal(FARGS,
279                               "%d GPU%s provided, but only %d PP thread-MPI thread%s coud be started.\n"
280                               "%s requires one PP tread-MPI thread per GPU; use fewer GPUs%s.",
281                               ngpu, gpu_plural, npppn, th_or_proc_plural,
282                               ShortProgram(), bMaxMpiThreadsSet ? "\nor allow more threads to be used" : "");
283                 }
284                 else
285                 {
286                     /* There are more GPUs than tMPI threads; we have to limit the number GPUs used. */
287                     md_print_warn(cr,fplog,
288                                   "NOTE: %d GPU%s were detected, but only %d PP thread-MPI thread%s can be started.\n"
289                                   "      %s can use one GPU per PP tread-MPI thread, so only %d GPU%s will be used.%s\n",
290                                   ngpu, gpu_plural, npppn, th_or_proc_plural,
291                                   ShortProgram(), npppn, npppn > 1 ? "s" : "",
292                                   bMaxMpiThreadsSet ? "\n      Also, you can allow more threads to be used by increasing GMX_MAX_MPI_THREADS" : "");
293
294                     if (cr->rank_pp_intranode == 0)
295                     {
296                         limit_num_gpus_used(hwinfo, npppn);
297                         ngpu = hwinfo->gpu_info.ncuda_dev_use;
298                         sprintf(gpu_plural, "%s", (ngpu > 1) ? "s" : "");
299                     }
300                 }
301             }
302         }
303
304         if (ngpu != npppn)
305         {
306             if (hwinfo->gpu_info.bUserSet)
307             {
308                 gmx_fatal(FARGS,
309                           "Incorrect launch configuration: mismatching number of PP %s%s and GPUs%s.\n"
310                           "%s was started with %d PP %s%s%s, but you provided %d GPU%s.",
311                           th_or_proc, btMPI ? "s" : "es" , pernode,
312                           ShortProgram(), npppn, th_or_proc, th_or_proc_plural, pernode, ngpu, gpu_plural);
313             }
314             else
315             {
316                 if (ngpu > npppn)
317                 {
318                     md_print_warn(cr,fplog,
319                                   "NOTE: potentially sub-optimal launch configuration, %s started with less\n"
320                                   "      PP %s%s%s than GPU%s available.\n"
321                                   "      Each PP %s can only use one GPU, so only %d GPU%s%s will be used.",
322                                   ShortProgram(),
323                                   th_or_proc, th_or_proc_plural, pernode, gpu_plural,
324                                   th_or_proc, npppn, gpu_plural, pernode);
325
326                     if (bMPI || (btMPI && cr->rank_pp_intranode == 0))
327                     {
328                         limit_num_gpus_used(hwinfo, npppn);
329                         ngpu = hwinfo->gpu_info.ncuda_dev_use;
330                         sprintf(gpu_plural, "%s", (ngpu > 1) ? "s" : "");
331                     }
332                 }
333                 else
334                 {
335                     /* Avoid duplicate error messages.
336                      * Unfortunately we can only do this at the physical node
337                      * level, since the hardware setup and MPI process count
338                      * might be differ over physical nodes.
339                      */
340                     if (cr->rank_pp_intranode == 0)
341                     {
342                         gmx_fatal(FARGS,
343                                   "Incorrect launch configuration: mismatching number of PP %s%s and GPUs%s.\n"
344                                   "%s was started with %d PP %s%s%s, but only %d GPU%s were detected.",
345                                   th_or_proc, btMPI ? "s" : "es" , pernode,
346                                   ShortProgram(), npppn, th_or_proc, th_or_proc_plural, pernode, ngpu, gpu_plural);
347                     }
348 #ifdef GMX_MPI
349                     else
350                     {
351                         /* Avoid other ranks to continue after inconsistency */
352                         MPI_Barrier(cr->mpi_comm_mygroup);
353                     }
354 #endif
355                 }
356             }
357         }
358
359         if (hwinfo->gpu_info.bUserSet && (cr->rank_pp_intranode == 0))
360         {
361             int i, j, same_count;
362             gmx_bool bSomeSame, bAllDifferent;
363
364             same_count = 0;
365             bSomeSame = FALSE;
366             bAllDifferent = TRUE;
367
368             for (i = 0; i < ngpu - 1; i++)
369             {
370                 for (j = i + 1; j < ngpu; j++)
371                 {
372                     bSomeSame       |= hwinfo->gpu_info.cuda_dev_use[i] == hwinfo->gpu_info.cuda_dev_use[j];
373                     bAllDifferent   &= hwinfo->gpu_info.cuda_dev_use[i] != hwinfo->gpu_info.cuda_dev_use[j];
374                     same_count      += hwinfo->gpu_info.cuda_dev_use[i] == hwinfo->gpu_info.cuda_dev_use[j];
375                 }
376             }
377
378             if (btMPI && !bAllDifferent)
379             {
380                 gmx_fatal(FARGS,
381                           "Invalid GPU assignment: can't share a GPU among multiple thread-MPI threads.\n"
382                           "Use MPI if you are sure that you want to assign GPU to multiple threads.");
383             }
384
385             if (bSomeSame)
386             {
387                 md_print_warn(cr,fplog,
388                               "NOTE: Potentially sub-optimal launch configuration: you assigned %s to\n"
389                               "      multiple %s%s; this should be avoided as it generally\n"
390                               "      causes performance loss.",
391                               same_count > 1 ? "GPUs" : "a GPU", th_or_proc, btMPI ? "s" : "es");
392             }
393         }
394         print_gpu_use_stats(fplog, &hwinfo->gpu_info, cr);
395     }
396 }
397
398 /* Return the number of hardware threads supported by the current CPU.
399  * We assume that this is equal with the number of CPUs reported to be
400  * online by the OS at the time of the call.
401  */
402 static int get_nthreads_hw_avail(FILE *fplog, const t_commrec *cr)
403 {
404      int ret = 0;
405
406 #if ((defined(WIN32) || defined( _WIN32 ) || defined(WIN64) || defined( _WIN64 )) && !(defined (__CYGWIN__) || defined (__CYGWIN32__)))
407     /* Windows */
408     SYSTEM_INFO sysinfo;
409     GetSystemInfo( &sysinfo );
410     ret = sysinfo.dwNumberOfProcessors;
411 #elif defined HAVE_SYSCONF
412     /* We are probably on Unix.
413      * Now check if we have the argument to use before executing the call
414      */
415 #if defined(_SC_NPROCESSORS_ONLN)
416     ret = sysconf(_SC_NPROCESSORS_ONLN);
417 #elif defined(_SC_NPROC_ONLN)
418     ret = sysconf(_SC_NPROC_ONLN);
419 #elif defined(_SC_NPROCESSORS_CONF)
420     ret = sysconf(_SC_NPROCESSORS_CONF);
421 #elif defined(_SC_NPROC_CONF)
422     ret = sysconf(_SC_NPROC_CONF);
423 #endif /* End of check for sysconf argument values */
424
425 #else
426     /* Neither windows nor Unix. No fscking idea how many CPUs we have! */
427     ret = -1;
428 #endif
429
430     if (debug)
431     {
432         fprintf(debug, "Detected %d processors, will use this as the number "
433                 "of supported hardware threads.\n", ret);
434     }
435
436 #ifdef GMX_OMPENMP
437     if (ret != gmx_omp_get_num_procs())
438     {
439         md_print_warn(cr, fplog,
440                       "Number of CPUs detected (%d) does not match the number reported by OpenMP (%d).\n"
441                       "Consider setting the launch configuration manually!",
442                       ret, gmx_omp_get_num_procs());
443     }
444 #endif
445
446     return ret;
447 }
448
449 void gmx_detect_hardware(FILE *fplog, gmx_hw_info_t *hwinfo,
450                          const t_commrec *cr,
451                          gmx_bool bForceUseGPU, gmx_bool bTryUseGPU,
452                          const char *gpu_id)
453 {
454     int             i;
455     const char      *env;
456     char            sbuf[STRLEN], stmp[STRLEN];
457     gmx_hw_info_t   *hw;
458     gmx_gpu_info_t  gpuinfo_auto, gpuinfo_user;
459     gmx_bool        bGPUBin;
460
461     assert(hwinfo);
462
463     /* detect CPUID info; no fuss, we don't detect system-wide
464      * -- sloppy, but that's it for now */
465     if (gmx_cpuid_init(&hwinfo->cpuid_info) != 0)
466     {
467         gmx_fatal_collective(FARGS, cr, NULL, "CPUID detection failed!");
468     }
469
470     /* detect number of hardware threads */
471     hwinfo->nthreads_hw_avail = get_nthreads_hw_avail(fplog, cr);
472
473     /* detect GPUs */
474     hwinfo->gpu_info.ncuda_dev_use  = 0;
475     hwinfo->gpu_info.cuda_dev_use   = NULL;
476     hwinfo->gpu_info.ncuda_dev      = 0;
477     hwinfo->gpu_info.cuda_dev       = NULL;
478
479 #ifdef GMX_GPU
480     bGPUBin      = TRUE;
481 #else
482     bGPUBin      = FALSE;
483 #endif
484
485     /* Bail if binary is not compiled with GPU acceleration, but this is either
486      * explicitly (-nb gpu) or implicitly (gpu ID passed) requested. */
487     if (bForceUseGPU && !bGPUBin)
488     {
489         gmx_fatal(FARGS, "GPU acceleration requested, but %s was compiled without GPU support!", ShortProgram());
490     }
491     if (gpu_id != NULL && !bGPUBin)
492     {
493         gmx_fatal(FARGS, "GPU ID string set, but %s was compiled without GPU support!", ShortProgram());
494     }
495
496     /* run the detection if the binary was compiled with GPU support */
497     if (bGPUBin && getenv("GMX_DISABLE_GPU_DETECTION")==NULL)
498     {
499         char detection_error[STRLEN];
500
501         if (detect_cuda_gpus(&hwinfo->gpu_info, detection_error) != 0)
502         {
503             if (detection_error != NULL && detection_error[0] != '\0')
504             {
505                 sprintf(sbuf, ":\n      %s\n", detection_error);
506             }
507             else
508             {
509                 sprintf(sbuf, ".");
510             }
511             md_print_warn(cr, fplog,
512                           "NOTE: Error occurred during GPU detection%s"
513                           "      Can not use GPU acceleration, will fall back to CPU kernels.\n",
514                           sbuf);
515         }
516     }
517
518     if (bForceUseGPU || bTryUseGPU)
519     {
520         env = getenv("GMX_GPU_ID");
521         if (env != NULL && gpu_id != NULL)
522         {
523             gmx_fatal(FARGS,"GMX_GPU_ID and -gpu_id can not be used at the same time");
524         }
525         if (env == NULL)
526         {
527             env = gpu_id;
528         }
529
530         /* parse GPU IDs if the user passed any */
531         if (env != NULL)
532         {
533             int *gpuid, *checkres;
534             int nid, res;
535
536             snew(gpuid, max_gpu_ids_user);
537             snew(checkres, max_gpu_ids_user);
538
539             parse_gpu_id_plain_string(env, &nid, gpuid);
540
541             if (nid == 0)
542             {
543                 gmx_fatal(FARGS, "Empty GPU ID string encountered.\n%s\n", invalid_gpuid_hint);
544             }
545
546             res = check_select_cuda_gpus(checkres, &hwinfo->gpu_info, gpuid, nid);
547
548             if (!res)
549             {
550                 print_gpu_detection_stats(fplog, &hwinfo->gpu_info, cr);
551
552                 sprintf(sbuf, "Some of the requested GPUs do not exist, behave strangely, or are not compatible:\n");
553                 for (i = 0; i < nid; i++)
554                 {
555                     if (checkres[i] != egpuCompatible)
556                     {
557                         sprintf(stmp, "    GPU #%d: %s\n",
558                                 gpuid[i], gpu_detect_res_str[checkres[i]]);
559                         strcat(sbuf, stmp);
560                     }
561                 }
562                 gmx_fatal(FARGS, "%s", sbuf);
563             }
564
565             hwinfo->gpu_info.bUserSet = TRUE;
566
567             sfree(gpuid);
568             sfree(checkres);
569         }
570         else
571         {
572             pick_compatible_gpus(&hwinfo->gpu_info);
573             hwinfo->gpu_info.bUserSet = FALSE;
574         }
575
576         /* decide whether we can use GPU */
577         hwinfo->bCanUseGPU = (hwinfo->gpu_info.ncuda_dev_use > 0);
578         if (!hwinfo->bCanUseGPU && bForceUseGPU)
579         {
580             gmx_fatal(FARGS, "GPU acceleration requested, but no compatible GPUs were detected.");
581         }
582     }
583 }
584
585 void limit_num_gpus_used(gmx_hw_info_t *hwinfo, int count)
586 {
587     int ndev_use;
588
589     assert(hwinfo);
590
591     ndev_use = hwinfo->gpu_info.ncuda_dev_use;
592
593     if (count > ndev_use)
594     {
595         /* won't increase the # of GPUs */
596         return;
597     }
598
599     if (count < 1)
600     {
601         char sbuf[STRLEN];
602         sprintf(sbuf, "Limiting the number of GPUs to <1 doesn't make sense (detected %d, %d requested)!",
603                 ndev_use, count);
604         gmx_incons(sbuf);
605     }
606
607     /* TODO: improve this implementation: either sort GPUs or remove the weakest here */
608     hwinfo->gpu_info.ncuda_dev_use = count;
609 }
610
611 void gmx_hardware_info_free(gmx_hw_info_t *hwinfo)
612 {
613     if (hwinfo)
614     {
615         gmx_cpuid_done(hwinfo->cpuid_info);
616         free_gpu_info(&hwinfo->gpu_info);
617         sfree(hwinfo);
618     }
619 }