775c2434ef62405ed2aceae87a519483181d283f
[alexxy/gromacs.git] / src / kernel / gmx_gpu_utils / gmx_gpu_utils.cu
1 /* -*- mode: c; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; c-file-style: "stroustrup"; -*-
2  *
3  * 
4  *                This source code is part of
5  * 
6  *                 G   R   O   M   A   C   S
7  * 
8  *          GROningen MAchine for Chemical Simulations
9  * 
10  * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
11  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
12  * Copyright (c) 2001-2010, The GROMACS development team,
13  * check out http://www.gromacs.org for more information.
14
15  * This program is free software; you can redistribute it and/or
16  * modify it under the terms of the GNU General Public License
17  * as published by the Free Software Foundation; either version 2
18  * of the License, or (at your option) any later version.
19  * 
20  * If you want to redistribute modifications, please consider that
21  * scientific software is very special. Version control is crucial -
22  * bugs must be traceable. We will be happy to consider code for
23  * inclusion in the official distribution, but derived work must not
24  * be called official GROMACS. Details are found in the README & COPYING
25  * files - if they are missing, get the official version at www.gromacs.org.
26  * 
27  * To help us fund GROMACS development, we humbly ask that you cite
28  * the papers on the package - you can find them in the top README file.
29  * 
30  * For more info, check our website at http://www.gromacs.org
31  * 
32  * And Hey:
33  * Gallium Rubidium Oxygen Manganese Argon Carbon Silicon
34  */
35
36 #include <stdio.h>
37 #include <stdlib.h>
38
39 #include "cuda.h"
40 #include "cuda_runtime_api.h"
41
42 #include "memtestG80_core.h"
43
44 /*! \cond  TEST */
45 #ifdef _DEBUG_
46 #undef _DEBUG_
47 #endif
48 #define _DEBUG_           0
49
50 #if _DEBUG_ >= 1
51 #define debug stderr 
52 #define DUPME(msg) printf("---> %s\n", msg);
53 #else
54 #define DUPME(msg) ;
55 #endif
56 /*! \endcond TEST*/
57
58 #if _DEBUG_ == 0/* no gromacs utils in debug mode */
59 #include "gmx_fatal.h"
60 #include "string2.h"
61 #endif
62
63 #define QUICK_MEM       250 /*!< Amount of memory to be used in quick memtest. */
64 #define QUICK_TESTS     MOD_20_32BIT | LOGIC_4_ITER_SHMEM | RANDOM_BLOCKS /*!< Bitflag with type of tests 
65                                                                             to run in quick memtest. */
66 #define QUICK_ITER      3 /*!< Number of iterations in quick memtest. */
67
68 #define FULL_TESTS      0x3FFF /*!<  Bitflag with all test set on for full memetest. */
69 #define FULL_ITER       25 /*!< Number of iterations in full memtest. */
70
71 #define TIMED_TESTS     MOD_20_32BIT | LOGIC_4_ITER_SHMEM | RANDOM_BLOCKS /*!< Bitflag with type of tests to 
72                                                                             run in time constrained memtest. */
73
74 /*! Number of supported GPUs */
75 #define NB_GPUS (sizeof(SupportedGPUs)/sizeof(SupportedGPUs[0]))
76
77 /*
78 TODO add proper gromacs logging?
79 */
80
81 /*! Bit-flags which refer to memtestG80 test types and are used in do_memtest to specify which tests to run. */
82 enum memtest_G80_test_types {
83     MOVING_INVERSIONS_10 =      0x1,
84     MOVING_INVERSIONS_RAND =    0x2,
85     WALKING_8BIT_M86 =          0x4,
86     WALKING_0_8BIT =            0x8,
87     WALKING_1_8BIT =            0x10,
88     WALKING_0_32BIT =           0x20,
89     WALKING_1_32BIT =           0x40,
90     RANDOM_BLOCKS =             0x80,
91     MOD_20_32BIT =              0x100,
92     LOGIC_1_ITER =              0x200,
93     LOGIC_4_ITER =              0x400,
94     LOGIC_1_ITER_SHMEM =        0x800,
95     LOGIC_4_ITER_SHMEM =        0x1000
96 };
97
98 // TODO put this list into an external file and include it so that the list is easily accessible
99 /*! List of supported GPUs. */
100 static const char * const SupportedGPUs[] = {
101     /* GT400 */
102     "Geforce GTX 480",
103     "Geforce GTX 470",
104
105     "Tesla C2070",
106     "Tesla C2050",
107     "Tesla S2070",
108     "Tesla S2050",
109
110     /* GT200 */
111     "Geforce GTX 295",
112     "Geforce GTX 285",
113     "Geforce GTX 280",
114     "Geforce GTX 275",
115     "Geforce GTX 260",
116     "GeForce GTS 250",
117     "GeForce GTS 150",
118
119     "GeForce GTX 285M",
120     "GeForce GTX 280M",
121
122     "Tesla S1070",
123     "Tesla C1060",
124     "Tesla M1060",
125
126     "Quadro FX 5800",
127     "Quadro FX 4800",
128     "Quadro CX",
129     "Quadro Plex 2200 D2",
130     "Quadro Plex 2200 S4",
131
132     /* G90 */
133     "GeForce 9800 G", /* GX2, GTX, GTX+, GT */
134     "GeForce 9800M GTX",
135
136     "Quadro FX 4700",
137     "Quadro Plex 2100 D4"
138 };
139
140 /*! \cond  TEST */
141 #ifndef _string2_h
142 /* debug functions, see @the end */
143 void ltrim (char *);
144 void rtrim (char *);
145 void trim  (char *);
146 int gmx_strncasecmp(const char*, const char*, int);
147 #endif
148 /*! \endcond  TEST */
149
150
151 /*! 
152   * \brief Runs GPU sanity checks.
153   * Returnes properties of a device with given id or the one that has
154   * already been initialized earlier in the case if of dev_id == -1.
155   *
156   * \param[in] dev_id       the device id of the GPU or -1 if the device has laredy been selected
157   * \param[out] dev_prop    pointer to the structure in which the device properties will be returned
158   */
159 static int do_sanity_checks(int dev_id, cudaDeviceProp *dev_prop)
160 {
161     cudaError_t cu_err;
162     int         dev_count, id;
163
164     cu_err = cudaGetDeviceCount(&dev_count);
165     if (cu_err != cudaSuccess)
166     {
167        fprintf(stderr, "Error %d while querying device count: %s\n", cu_err,
168                cudaGetErrorString(cu_err));
169         return -1;
170     }
171
172     /* no CUDA compatible device at all */
173     if (dev_count == 0)
174         return -1;
175
176     /* things might go horribly wrong if cudart is not compatible with the driver */
177     if (dev_count < 0 || dev_count > 20)
178         return -1;
179
180     if (dev_id == -1) /* device already selected let's do not destroy the context */
181     {
182         cu_err = cudaGetDevice(&id);
183         if (cu_err != cudaSuccess)
184         {
185             fprintf(stderr, "Error %d while querying device id: %s\n", cu_err,
186                     cudaGetErrorString(cu_err));
187             return -1;
188         }
189     }
190     else
191     {
192         id = dev_id;
193         if (id > dev_count - 1) /* pfff there's no such device */
194         {
195             fprintf(stderr, "The requested device with id %d does not seem to exist (device count=%d)\n",
196                     dev_id, dev_count);
197             return -1;
198         }
199     }
200
201     memset(dev_prop, 0, sizeof(cudaDeviceProp));
202     cu_err = cudaGetDeviceProperties(dev_prop, id);
203     if (cu_err != cudaSuccess)
204     {
205         fprintf(stderr, "Error %d while querying device properties: %s\n", cu_err,
206                 cudaGetErrorString(cu_err));
207         return -1;
208     }
209
210     /* both major & minor is 9999 if no CUDA capable devices are present */
211     if (dev_prop->major == 9999 && dev_prop->minor == 9999)
212         return -1;
213     /* we don't care about emulation mode */
214     if (dev_prop->major == 0)
215         return -1;
216
217     if ((dev_id != -1) && (cu_err = cudaSetDevice(dev_id)) != cudaSuccess)
218     {
219         fprintf(stderr, "Error %d while switching to device #%d: %s\n", cu_err, dev_id,
220                 cudaGetErrorString(cu_err));
221         return -1;
222     }
223
224     return 0;
225 }
226
227 /*! 
228  * \brief Checks whether the GPU with the given name is supported.
229  * 
230  * \param[in] gpu_name  the name of the CUDA device
231  * \returns             1 if the device is supported, otherwise 0
232  */
233 static int is_supported_gpu_n(char *gpuName)
234 {
235     size_t i;
236     for (i = 0; i < NB_GPUS; i++)
237     {
238         trim(gpuName);
239         if (gmx_strncasecmp(gpuName, SupportedGPUs[i], strlen(SupportedGPUs[i])) == 0)
240             return 1;
241     }
242     return 0;
243 }
244
245 /*! \brief Checks whether the GPU with the given device id is supported. 
246  *
247  * \param[in] dev_id    the device id of the GPU or -1 if the device has laredy been selected
248  * \param[out] gpu_name Set to contain the name of the CUDA device, if NULL passed, no device name is set. 
249  * \returns             1 if the device is supported, otherwise 0
250  */
251 int is_supported_cuda_gpu(int dev_id, char *gpu_name)
252 {
253     cudaDeviceProp dev_prop;
254
255     if (debug) fprintf(debug, "Checking compatibility with device #%d, %s\n", dev_id, gpu_name);
256
257     if (do_sanity_checks(dev_id, &dev_prop) != 0)
258         return -1;
259
260     if (gpu_name != NULL)
261     { 
262         strcpy(gpu_name, dev_prop.name);
263     }
264     return is_supported_gpu_n(dev_prop.name);
265 }
266
267
268 /*!
269  * \brief Runs a set of memory tests specified by the given bit-flags.
270  * Tries to allocate and do the test on \p megs Mb memory or 
271  * the greatest amount that can be allocated (>10Mb).
272  * In case if an error is detected it stops without finishing the remainings 
273  * steps/iterations and returns greater then zero value.  
274  * In case of other errors (e.g. kernel launch errors, device querying erros) 
275  * -1 is returned.
276  *
277  * \param[in] which_tests   variable with bit-flags of the requested tests
278  * \param[in] megs          amount of memory that will be tested in MB
279  * \param[in] iter          number of iterations
280  * \returns                 0 if no error was detected, otherwise >0
281  */
282 static int do_memtest(unsigned int which_tests, int megs, int iter)
283 {
284     memtestState    tester;
285     int             i;
286     uint            err_count; //, err_iter;
287
288     // no parameter check as this fn won't be called externally
289
290     // let's try to allocate the mem
291     while (!tester.allocate(megs) && (megs - 10 > 0))
292         { megs -= 10; tester.deallocate(); }
293
294     if (megs <= 10)
295     {
296         fprintf(stderr, "Unable to allocate GPU memory!\n");
297         return -1;
298     }
299
300     // clear the first 18 bits
301     which_tests &= 0x3FFF;
302     for (i = 0; i < iter; i++)
303     {
304         // Moving Inversions (ones and zeros)
305         if ((MOVING_INVERSIONS_10 & which_tests) == MOVING_INVERSIONS_10)
306         {
307             tester.gpuMovingInversionsOnesZeros(err_count);
308             if (err_count > 0)
309                 return MOVING_INVERSIONS_10;
310         }
311         // Moving Inversions (random)
312         if ((MOVING_INVERSIONS_RAND & which_tests) == MOVING_INVERSIONS_RAND)
313         {
314             tester.gpuMovingInversionsRandom(err_count);
315             if (err_count > 0)
316                 return MOVING_INVERSIONS_RAND;
317         }
318        // Memtest86 Walking 8-bit
319         if ((WALKING_8BIT_M86 & which_tests) == WALKING_8BIT_M86)
320         {
321             for (uint shift = 0; shift < 8; shift++)
322             {
323                 tester.gpuWalking8BitM86(err_count, shift);
324                 if (err_count > 0)
325                     return WALKING_8BIT_M86;
326             }
327       }
328         // True Walking zeros (8-bit)
329         if ((WALKING_0_8BIT & which_tests) == WALKING_0_8BIT)
330         {
331             for (uint shift = 0; shift < 8; shift++)
332             {
333                 tester.gpuWalking8Bit(err_count, false, shift);
334                 if (err_count > 0)
335                     return WALKING_0_8BIT;
336             }
337         }
338         // True Walking ones (8-bit)
339         if ((WALKING_1_8BIT & which_tests) == WALKING_1_8BIT)
340         {
341             for (uint shift = 0; shift < 8; shift++)
342             {
343                 tester.gpuWalking8Bit(err_count, true, shift);
344                 if (err_count > 0)
345                     return WALKING_1_8BIT;
346             }
347         }
348         // Memtest86 Walking zeros (32-bit)
349         if ((WALKING_0_32BIT & which_tests) == WALKING_0_32BIT)
350         {
351             for (uint shift = 0; shift < 32; shift++)
352             {
353                 tester.gpuWalking32Bit(err_count, false, shift);
354                 if (err_count > 0)
355                     return WALKING_0_32BIT;
356             }
357         }
358        // Memtest86 Walking ones (32-bit)
359         if ((WALKING_1_32BIT & which_tests) == WALKING_1_32BIT)
360         {
361             for (uint shift = 0; shift < 32; shift++)
362             {
363                 tester.gpuWalking32Bit(err_count, true, shift);
364                 if (err_count > 0)
365                     return WALKING_1_32BIT;
366             }
367        }
368         // Random blocks
369         if ((RANDOM_BLOCKS & which_tests) == RANDOM_BLOCKS)
370         {
371             tester.gpuRandomBlocks(err_count,rand());
372             if (err_count > 0)
373                 return RANDOM_BLOCKS;
374
375         }
376
377         // Memtest86 Modulo-20
378         if ((MOD_20_32BIT & which_tests) == MOD_20_32BIT)
379         {
380             for (uint shift = 0; shift < 20; shift++)
381             {
382                 tester.gpuModuloX(err_count, shift, rand(), 20, 2);
383                 if (err_count > 0)
384                     return MOD_20_32BIT;
385             }
386         }
387         // Logic (one iteration)
388         if ((LOGIC_1_ITER & which_tests) == LOGIC_1_ITER)
389         {
390             tester.gpuShortLCG0(err_count,1);
391             if (err_count > 0)
392                 return LOGIC_1_ITER;
393         }
394         // Logic (4 iterations)
395         if ((LOGIC_4_ITER & which_tests) == LOGIC_4_ITER)
396         {
397             tester.gpuShortLCG0(err_count,4);
398             if (err_count > 0)
399                 return LOGIC_4_ITER;
400
401         }
402         // Logic (shared memory, one iteration)
403         if ((LOGIC_1_ITER_SHMEM & which_tests) == LOGIC_1_ITER_SHMEM)
404         {
405             tester.gpuShortLCG0Shmem(err_count,1);
406             if (err_count > 0)
407                 return LOGIC_1_ITER_SHMEM;
408         }
409         // Logic (shared-memory, 4 iterations)
410         if ((LOGIC_4_ITER_SHMEM & which_tests) == LOGIC_4_ITER_SHMEM)
411         {
412             tester.gpuShortLCG0Shmem(err_count,4);
413             if (err_count > 0)
414                 return LOGIC_4_ITER_SHMEM;
415         }
416     }
417
418     tester.deallocate();
419     return err_count;
420 }
421
422 /*! \brief Runs a quick memory test and returns 0 in case if no error is detected. 
423  * If an error is detected it stops before completing the test and returns a 
424  * value greater then 0. In case of other errors (e.g. kernel launch errors, 
425  * device querying erros) -1 is returned.
426  *
427  * \param[in] dev_id    the device id of the GPU or -1 if the device has laredy been selected
428  * \returns             0 if no error was detected, otherwise >0
429  */
430 int do_quick_memtest(int dev_id)
431 {
432     cudaDeviceProp  dev_prop;
433     int             devmem, res, time=0;
434
435     if (debug) { time = getTimeMilliseconds(); }
436
437     if (do_sanity_checks(dev_id, &dev_prop) != 0)
438     {
439         // something went wrong
440         return -1;
441     }
442
443     if (debug)
444     {
445         devmem = dev_prop.totalGlobalMem/(1024*1024); // in MiB
446         fprintf(debug, ">> Running QUICK memtests on %d MiB (out of total %d MiB), %d iterations\n",
447             QUICK_MEM, devmem, QUICK_ITER);
448     }
449
450     res = do_memtest(QUICK_TESTS, QUICK_MEM, QUICK_ITER);
451
452     if (debug)
453     {
454         fprintf(debug, "Q-RES = %d\n", res);
455         fprintf(debug, "Q-runtime: %d ms\n", getTimeMilliseconds() - time);
456     }
457
458     /* destroy context only if we created it */
459     if (dev_id !=-1) cudaThreadExit();
460     return res;
461 }
462
463 /*! \brief Runs a full memory test and returns 0 in case if no error is detected. 
464  * If an error is detected  it stops before completing the test and returns a 
465  * value greater then 0. In case of other errors (e.g. kernel launch errors, 
466  * device querying erros) -1 is returned.
467  *
468  * \param[in] dev_id    the device id of the GPU or -1 if the device has laredy been selected
469  * \returns             0 if no error was detected, otherwise >0
470  */
471
472 int do_full_memtest(int dev_id)
473 {
474     cudaDeviceProp  dev_prop;
475     int             devmem, res, time=0;
476
477     if (debug) { time = getTimeMilliseconds(); }
478
479     if (do_sanity_checks(dev_id, &dev_prop) != 0)
480     {
481         // something went wrong
482         return -1;
483     }
484
485     devmem = dev_prop.totalGlobalMem/(1024*1024); // in MiB
486
487     if (debug) 
488     { 
489         fprintf(debug, ">> Running FULL memtests on %d MiB (out of total %d MiB), %d iterations\n",
490             devmem, devmem, FULL_ITER); 
491     }
492
493     /* do all test on the entire memory */
494     res = do_memtest(FULL_TESTS, devmem, FULL_ITER);
495
496     if (debug)
497     {
498         fprintf(debug, "F-RES = %d\n", res);
499         fprintf(debug, "F-runtime: %d ms\n", getTimeMilliseconds() - time);
500     }
501
502     /* destroy context only if we created it */
503     if (dev_id != -1) cudaThreadExit();
504     return res;
505 }
506
507 /*! \brief Runs a time constrained memory test and returns 0 in case if no error is detected.
508  * If an error is detected it stops before completing the test and returns a value greater 
509  * than zero. In case of other errors (e.g. kernel launch errors, device querying erros) -1 
510  * is returned. Note, that test iterations are not interrupted therefor the total runtime of 
511  * the test will always be multipple of one iteration's runtime.
512  *
513  * \param[in] dev_id        the device id of the GPU or -1 if the device has laredy been selected
514  * \param[in] time_constr   the time limit of the testing
515  * \returns                 0 if no error was detected, otherwise >0
516  */
517 int do_timed_memtest(int dev_id, int time_constr)
518 {
519     cudaDeviceProp  dev_prop;
520     int             devmem, res=0, time=0, startt;
521
522     if (debug) { time = getTimeMilliseconds(); }
523
524     time_constr *= 1000;  /* convert to ms for convenience */
525     startt = getTimeMilliseconds();
526
527     if (do_sanity_checks(dev_id, &dev_prop) != 0)
528     {
529         // something went wrong
530         return -1;
531     }
532
533     devmem = dev_prop.totalGlobalMem/(1024*1024); // in MiB
534
535     if (debug) 
536     { 
537         fprintf(debug, ">> Running time constrained memtests on %d MiB (out of total %d MiB), time limit of %d s \n",
538         devmem, devmem, time_constr); 
539     }
540
541     /* do the TIMED_TESTS set, one step at a time on the entire memory 
542        that can be allocated, and stop when the given time is exceeded */
543     while ( ((int)getTimeMilliseconds() - startt) < time_constr)
544     {        
545         res = do_memtest(TIMED_TESTS, devmem, 1);
546         if (res != 0) break;
547     }
548
549     if (debug)
550     {
551         fprintf(debug, "T-RES = %d\n", res);
552         fprintf(debug, "T-runtime: %d ms\n", getTimeMilliseconds() - time);
553     }
554
555     /* destroy context only if we created it */
556     if (dev_id != -1) cudaThreadExit();
557     return res;
558 }
559
560 /*! \cond TEST */
561
562 /*******************************************************
563  * The code below is for testing purposes. */
564 int do_custom_memtest(int dev_id)
565 {
566     cudaDeviceProp  dev_prop;
567     int             mem2test, /*devmem,*/ res;
568 //    memtestState    tester;
569 //    double          bandwidth;
570
571 #if _DEBUG_ >= 1
572     int time = getTimeMilliseconds();
573 #endif
574
575    if (do_sanity_checks(dev_id, &dev_prop) != 0)
576         return -1;
577
578 //    if ((res=tester.allocate(100))==0)
579 //        printf("alloc failed\n");
580 //    printf("alloc res = %d\n", res);
581 //    res = tester.gpuMemoryBandwidth(bandwidth, tester.size(), 10);
582 //    printf("Bandwidth on %d (res %d)= %5.2f\n", tester.size(), res, bandwidth);
583 //    tester.deallocate();
584
585 //    devmem   = dev_prop.totalGlobalMem/(1024*1024); // in MiB
586     mem2test = 80;
587
588 #if _DEBUG_ >= 1
589     printf(">> Running CUSTOM memtests [%x] on %d MiB, %d iterations\n",
590         QUICK_TESTS, mem2test, 1);
591 #endif
592
593     res = do_memtest(QUICK_TESTS, mem2test, 1);
594     cudaThreadExit();
595
596 #if _DEBUG_ >= 1
597     printf("C-RES = %d\n", res);
598     printf("C-runtime: %d ms\n", getTimeMilliseconds() - time);
599 #endif
600     return res;
601 }
602
603 #if _DEBUG_ > 1
604 /*!
605  * Only for debugging purposes, compile with:
606  * nvcc -DLINUX -D_DEBUG_=2  -L -O  -Xcompiler -Wall memtestG80_core.o gmx_gpu_utils.cu  -o gmx_gpu_utils_test
607  */
608 int main( int argc, char** argv)
609 {
610     int dev_id = 0;
611     char msg[100];
612     sprintf(msg, "Device #%d supported: ", dev_id);
613     switch (is_supported_cuda_gpu(dev_id, NULL))
614     {
615         case -1: strcat(msg, "error occured"); break;
616         case  0: strcat(msg, "no"); break;
617         case  1: strcat(msg, "yes"); break;
618         default: strcat(msg, "\nhmmm, you should not see this!");
619     }
620     printf("%s\n", msg);
621
622     printf("Doing memtest.\n");
623     printf("quick memtest result: %d\n", do_quick_memtest(dev_id));
624     printf("timed memtest result: %d\n", do_timed_memtest(dev_id, 15));
625     printf("full memtest result: %d\n", do_full_memtest(dev_id));
626     return 0;
627 }
628 #endif
629
630
631 #ifndef _string2_h
632 #include <string.h>
633 /* 
634     Functions only used if this file is compiled in debug mode (_DEBUG_ > 0)
635     when the gromacs version are not available.
636     - string trimming function - duplicated from ~/src/gmxlib/string2.c 
637     - case agnostic straing compare
638  */
639 static void ltrim (char *str)
640 {
641   char *tr;
642   int c;
643
644   if (!str)
645     return;
646
647   tr = strdup (str);
648   c  = 0;
649   while ((tr[c] == ' ') || (tr[c] == '\t'))
650     c++;
651
652   strcpy (str,tr+c);
653   free (tr);
654 }
655
656 static void rtrim (char *str)
657 {
658   int nul;
659
660   if (!str)
661     return;
662
663   nul = strlen(str)-1;
664   while ((nul > 0) && ((str[nul] == ' ') || (str[nul] == '\t')) ) {
665     str[nul] = '\0';
666     nul--;
667   }
668 }
669
670 static void trim (char *str)
671 {
672   ltrim (str);
673   rtrim (str);
674 }
675
676 static int gmx_strncasecmp(const char* s1, const char* s2, int len)
677 {
678   return strncasecmp(s1, s2, len);
679 }
680 #endif
681
682 /*! \endcond TEST */