automation for setting GMX_GPU & cmake GPU detection
[alexxy/gromacs.git] / cmake / gmxDetectGpu.cmake
1 # The gmx_detect_gpu() macro aims to detect GPUs available in the build machine
2 # and provide the number, names, and compute-capabilities of these devices.
3 #
4 # The current version is limited to checking the availability of NVIDIA GPUs
5 # without compute-capability information.
6 #
7 # The current detection relies on the following checks in the order of listing:
8 # - output of nvidia-smi (if available);
9 # - presence and content of of /proc/driver/nvidia/gpus/*/information (Linux)
10 # - output of lspci (Linux)
11 #
12 # If any of the checks succeeds in finding devices, consecutive checks will not
13 # be carried out. Additionally, when lspci is used and a device with unknown
14 # PCI ID is encountered, lspci tries to check the online PCI ID database. If
15 # this is not possible or the device is simply not recognized, no device names
16 # will be available.
17 #
18 # The following advanced variables are defined:
19 # - GMX_DETECT_GPU_AVAILABLE - TRUE if any GPUs were detected, otherwise FALSE
20 # - GMX_DETECT_GPU_COUNT     - # of GPUs detected
21 # - GMX_DETECT_GPU_INFO      - list of information strings of the detected GPUs
22 #
23 # NOTE: The proper solution is to detect hardware compatible with the native
24 # GPU acceleration. However, this requires checking the compute capability
25 # of the device which is not possible with the current checks and requires
26 # interfacing with the CUDA driver API.
27 #
28
29 # check whether the number of GPUs machetes the number of elements in the GPU info list
30 macro(check_num_gpu_info NGPU GPU_INFO)
31     list(LENGTH ${GPU_INFO} _len)
32     if (NOT NGPU EQUAL _len)
33         list(APPEND ${GMX_DETECT_GPU_INFO} "NOTE: information about some GPU(s) missing!")
34     endif()
35 endmacro()
36
37 macro(gmx_detect_gpu)
38
39     if (NOT DEFINED GMX_DETECT_GPU_COUNT OR NOT DEFINED GMX_DETECT_GPU_INFO)
40
41         set(GMX_DETECT_GPU_COUNT 0)
42         set(GMX_DETECT_GPU_INFO  "")
43
44         message(STATUS "Looking for NVIDIA GPUs present in the system")
45
46         # nvidia-smi-based detection.
47         # Requires the nvidia-smi tool to be installed and available in the path
48         # or in one of the default search locations
49         if (NOT DEFINED GMX_DETECT_GPU_COUNT_NVIDIA_SMI)
50             # try to find the nvidia-smi binary
51             # TODO add location hints
52             find_program(_nvidia_smi "nvidia-smi")
53             if (_nvidia_smi)
54                 set(GMX_DETECT_GPU_COUNT_NVIDIA_SMI 0)
55                 # execute nvidia-smi -L to get a short list of GPUs available
56                 exec_program(${_nvidia_smi_path} ARGS -L
57                     OUTPUT_VARIABLE _nvidia_smi_out
58                     RETURN_VALUE    _nvidia_smi_ret)
59                 # process the stdout of nvidia-smi
60                 if (_nvidia_smi_ret EQUAL 0)
61                     # convert string with newlines to list of strings
62                     string(REGEX REPLACE "\n" ";" _nvidia_smi_out "${_nvidia_smi_out}")
63                     foreach(_line ${_nvidia_smi_out})
64                         if (_line MATCHES "^GPU [0-9]+:")
65                             math(EXPR GMX_DETECT_GPU_COUNT_NVIDIA_SMI "${GMX_DETECT_GPU_COUNT_NVIDIA_SMI}+1")
66                             # the UUID is not very useful for the user, remove it
67                             string(REGEX REPLACE " \\(UUID:.*\\)" "" _gpu_info "${_line}")
68                             if (NOT _gpu_info STREQUAL "")
69                                 list(APPEND GMX_DETECT_GPU_INFO "${_gpu_info}")
70                             endif()
71                         endif()
72                     endforeach()
73
74                     check_num_gpu_info(${GMX_DETECT_GPU_COUNT_NVIDIA_SMI} GMX_DETECT_GPU_INFO)
75                     set(GMX_DETECT_GPU_COUNT ${GMX_DETECT_GPU_COUNT_NVIDIA_SMI})
76                 endif()
77             endif()
78
79             unset(_nvidia_smi CACHE)
80             unset(_nvidia_smi_ret)
81             unset(_nvidia_smi_out)
82             unset(_gpu_name)
83             unset(_line)
84         endif()
85
86         if (UNIX AND NOT (APPLE OR CYGWIN))
87             # /proc/driver/nvidia/gpus/*/information-based detection.
88             # Requires the NVDIA closed source driver to be installed and loaded
89             if (NOT DEFINED GMX_DETECT_GPU_COUNT_PROC AND GMX_DETECT_GPU_COUNT EQUAL 0)
90                 set(GMX_DETECT_GPU_COUNT_PROC 0)
91                 file(GLOB _proc_nv_gpu_info "/proc/driver/nvidia/gpus/*/information")
92                 foreach (_file ${_proc_nv_gpu_info})
93                     math(EXPR GMX_DETECT_GPU_COUNT_PROC "${GMX_DETECT_GPU_COUNT_PROC}+1")
94                     # assemble information strings similar to the nvidia-smi output
95                     # GPU ID = directory name on /proc/driver/nvidia/gpus/
96                     string(REGEX REPLACE "/proc/driver/nvidia/gpus.*([0-9]+).*information" "\\1" _gpu_id ${_file})
97                     # GPU name
98                     file(STRINGS ${_file} _gpu_name LIMIT_COUNT 1 REGEX "^Model:.*" NO_HEX_CONVERSION)
99                     string(REGEX REPLACE "^Model:[ \t]*(.*)" "\\1" _gpu_name "${_gpu_name}")
100                     if (NOT _gpu_id STREQUAL "" AND NOT _gpu_name STREQUAL "")
101                         list(APPEND GMX_DETECT_GPU_INFO "GPU ${_gpu_id}: ${_gpu_name}")
102                     endif()
103                 endforeach()
104
105                 check_num_gpu_info(${GMX_DETECT_GPU_COUNT_PROC} GMX_DETECT_GPU_INFO)
106                 set(GMX_DETECT_GPU_COUNT ${GMX_DETECT_GPU_COUNT_PROC})
107
108                 unset(_proc_nv_gpu_info)
109                 unset(_gpu_name)
110                 unset(_gpu_id)
111                 unset(_file)
112             endif()
113
114             # lspci-based detection (does not provide GPU information).
115             # Requires lspci and for GPU names to be fetched from the central
116             # PCI ID db if not available locally.
117             if (NOT DEFINED GMX_DETECT_GPU_COUNT_LSPCI AND GMX_DETECT_GPU_COUNT EQUAL 0)
118                 set(GMX_DETECT_GPU_COUNT_LSPCI 0)
119                 exec_program(lspci ARGS -q
120                     OUTPUT_VARIABLE _lspci_out
121                     RETURN_VALUE    _lspci_ret)
122                 # prehaps -q is not supported, try running without
123                 if (NOT RETURN_VALUE EQUAL 0)
124                     exec_program(lspci
125                         OUTPUT_VARIABLE _lspci_out
126                         RETURN_VALUE    _lspci_ret)
127                 endif()
128                 if (_lspci_ret EQUAL 0)
129                     # convert string with newlines to list of strings
130                     STRING(REGEX REPLACE ";" "\\\\;" _lspci_out "${_lspci_out}")
131                     string(REGEX REPLACE "\n" ";" _lspci_out "${_lspci_out}")
132                     foreach(_line ${_lspci_out})
133                         string(TOUPPER "${_line}" _line_upper)
134                         if (_line_upper MATCHES ".*VGA.*NVIDIA.*" OR _line_upper MATCHES ".*3D.*NVIDIA.*")
135                             math(EXPR GMX_DETECT_GPU_COUNT_LSPCI "${GMX_DETECT_GPU_COUNT_LSPCI}+1")
136                             # Try to parse out the device name which should be
137                             # included in the lspci -q output between []-s
138                             string(REGEX REPLACE ".*\\[(.*)\\].*" "\\1" _gpu_name "${_line}")
139                             if (NOT _gpu_name EQUAL "")
140                                 list(APPEND GMX_DETECT_GPU_INFO "${_gpu_name}")
141                             endif()
142                         endif()
143                     endforeach()
144
145                     check_num_gpu_info(${GMX_DETECT_GPU_COUNT_LSPCI} GMX_DETECT_GPU_INFO)
146                     set(GMX_DETECT_GPU_COUNT ${GMX_DETECT_GPU_COUNT_LSPCI})
147                 endif()
148
149                 unset(_lspci_ret)
150                 unset(_lspci_out)
151                 unset(_gpu_name)
152                 unset(_line)
153                 unset(_line_upper)
154             endif()
155         endif()
156
157         if (GMX_DETECT_GPU_COUNT GREATER 0)
158             set(GMX_DETECT_GPU_AVAILABLE YES)
159         else()
160             set(GMX_DETECT_GPU_AVAILABLE NO)
161         endif()
162         set(GMX_DETECT_GPU_AVAILABLE YES CACHE BOOL "Whether any NVIDIA GPU was detected" FORCE)
163
164         set(GMX_DETECT_GPU_COUNT ${GMX_DETECT_GPU_COUNT}
165             CACHE STRING "Number of NVIDIA GPUs detected")
166         set(GMX_DETECT_GPU_INFO ${GMX_DETECT_GPU_INFO}
167             CACHE STRING "basic information on the detected NVIDIA GPUs")
168
169         set(GMX_DETECT_GPU_COUNT_NVIDIA_SMI ${GMX_DETECT_GPU_COUNT_NVIDIA_SMI}
170             CACHE INTERNAL "Number of NVIDIA GPUs detected using nvidia-smi")
171         set(GMX_DETECT_GPU_COUNT_PROC ${GMX_DETECT_GPU_COUNT_PROC}
172             CACHE INTERNAL "Number of NVIDIA GPUs detected in /proc/driver/nvidia/gpus")
173         set(GMX_DETECT_GPU_COUNT_LSPCI ${GMX_DETECT_GPU_COUNT_LSPCI}
174             CACHE INTERNAL "Number of NVIDIA GPUs detected using lspci")
175
176         mark_as_advanced(GMX_DETECT_GPU_AVAILABLE
177                          GMX_DETECT_GPU_COUNT
178                          GMX_DETECT_GPU_INFO)
179
180         if (GMX_DETECT_GPU_AVAILABLE)
181             message(STATUS "Number of NVIDIA GPUs detected: ${GMX_DETECT_GPU_COUNT} ")
182         else()
183             message(STATUS "Could not detect NVIDIA GPUs")
184         endif()
185
186     endif (NOT DEFINED GMX_DETECT_GPU_COUNT OR NOT DEFINED GMX_DETECT_GPU_INFO)
187 endmacro(gmx_detect_gpu)