أسهل طريقة لاختبار لوجود وحدة معالجة الرسومات CUDA قادرة على cmake؟
-
21-09-2019 - |
سؤال
لدينا بعض آلات البناء الليلية التي لديها مكتبات CUDA تم تثبيته ، ولكن ليس لديه وحدة معالجة الرسومات CUDA التي يمكن تثبيتها. هذه الآلات قادرة على بناء برامج تدعم CUDA ، لكنها ليست قادرة على إدارة هذه البرامج.
في عملية الإنشاء الليلية الآلية لدينا ، تستخدم البرامج النصية الخاصة بنا CMAKE أمر CMAKE
find_package(CUDA)
لتحديد ما إذا تم تثبيت برنامج CUDA. هذا يضع متغير CMake CUDA_FOUND
على المنصات التي تم تثبيت برنامج CUDA. هذا رائع ويعمل بشكل مثالي. متى CUDA_FOUND
تم تعيينه ، لا بأس في بناء برامج تدعم CUDA. حتى عندما لا يحتوي الجهاز على وحدة معالجة الرسومات CUDA.
لكن برامج الاختبار التي تستخدم CUDA تفشل بشكل طبيعي على آلات CUDA غير GPU ، مما تسبب في تبدو لوحات المعلومات الليلية "قذرة". لذلك أريد أن يتجنب Cmake إجراء هذه الاختبارات على هذه الآلات. لكنني ما زلت أرغب في بناء برنامج CUDA على تلك الآلات.
بعد الحصول على إيجابية CUDA_FOUND
النتيجة ، أود اختبار وجود وحدة معالجة الرسومات الفعلية ، ثم ضبط متغير ، على سبيل المثال CUDA_GPU_FOUND
, ، لتعكس هذا.
ما هي أبسط طريقة للحصول على CMAKE لاختبار وجود وحدة معالجة الرسومات القادر على CUDA؟
يحتاج هذا إلى العمل على ثلاثة منصات: Windows مع MSVC و Mac و Linux. (لهذا السبب نستخدم Cmake في المقام الأول)
تعديل: هناك بضعة اقتراحات جيدة المظهر في الإجابات حول كيفية كتابة برنامج لاختبار لوجود وحدة معالجة الرسومات. ما لا يزال مفقودًا هو وسيلة الحصول على cmake لتجميع وتشغيل هذا البرنامج في وقت التكوين. أظن أن TRY_RUN
سيكون الأمر في Cmake أمرًا بالغ الأهمية هنا ، ولكن لسوء الحظ هذا الأمر تقريبا غير موثقة, ، ولا يمكنني معرفة كيفية جعلها تعمل. قد يكون هذا الجزء Cmake من المشكلة سؤالًا أكثر صعوبة. ربما كان ينبغي علي طرح هذا كأسفين منفصلين ...
المحلول
تتكون الإجابة على هذا السؤال من جزأين:
- برنامج لاكتشاف وجود وحدة معالجة الرسومات CUDA قادرة على cuda.
- رمز CMAKE لتجميع وتشغيل وتفسير نتيجة هذا البرنامج في وقت التكوين.
بالنسبة للجزء 1 ، برنامج استنشاق GPU ، بدأت بالإجابة التي قدمها Fabriziom لأنها مضغوطة للغاية. اكتشفت بسرعة أنني بحاجة إلى العديد من التفاصيل الموجودة في إجابة Unknown لجعلها تعمل بشكل جيد. ما انتهى بي الأمر هو الملف المصدر C التالي ، الذي سميته has_cuda_gpu.c
:
#include <stdio.h>
#include <cuda_runtime.h>
int main() {
int deviceCount, device;
int gpuDeviceCount = 0;
struct cudaDeviceProp properties;
cudaError_t cudaResultCode = cudaGetDeviceCount(&deviceCount);
if (cudaResultCode != cudaSuccess)
deviceCount = 0;
/* machines with no GPUs can still report one emulation device */
for (device = 0; device < deviceCount; ++device) {
cudaGetDeviceProperties(&properties, device);
if (properties.major != 9999) /* 9999 means emulation only */
++gpuDeviceCount;
}
printf("%d GPU CUDA device(s) found\n", gpuDeviceCount);
/* don't just return the number of gpus, because other runtime cuda
errors can also yield non-zero return values */
if (gpuDeviceCount > 0)
return 0; /* success */
else
return 1; /* failure */
}
لاحظ أن رمز الإرجاع هو صفر في الحالة التي يتم فيها العثور على وحدة معالجة الرسومات التي تدعم CUDA. هذا لأنه في إحدى آلات HAS-CUDA ولكن لا GPU ، يولد هذا البرنامج خطأ في وقت التشغيل مع رمز الخروج غير الصفر. لذلك يتم تفسير أي رمز خروج غير صفري على أنه "لا يعمل CUDA على هذا الجهاز".
قد تسأل لماذا لا أستخدم وضع مضاهاة CUDA على آلات غير GPU. ذلك لأن وضع المحاكاة هو عربات التي تجرها الدواب. أريد فقط تصحيح الكود الخاص بي ، وأعمل حول الأخطاء في كود GPU CUDA. ليس لدي وقت لتصحيح المحاكي.
الجزء الثاني من المشكلة هو رمز CMAKE لاستخدام برنامج الاختبار هذا. بعد بعض النضال ، اكتشفت ذلك. الكتلة التالية جزء من أكبر CMakeLists.txt
ملف:
find_package(CUDA)
if(CUDA_FOUND)
try_run(RUN_RESULT_VAR COMPILE_RESULT_VAR
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/has_cuda_gpu.c
CMAKE_FLAGS
-DINCLUDE_DIRECTORIES:STRING=${CUDA_TOOLKIT_INCLUDE}
-DLINK_LIBRARIES:STRING=${CUDA_CUDART_LIBRARY}
COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT_VAR
RUN_OUTPUT_VARIABLE RUN_OUTPUT_VAR)
message("${RUN_OUTPUT_VAR}") # Display number of GPUs found
# COMPILE_RESULT_VAR is TRUE when compile succeeds
# RUN_RESULT_VAR is zero when a GPU is found
if(COMPILE_RESULT_VAR AND NOT RUN_RESULT_VAR)
set(CUDA_HAVE_GPU TRUE CACHE BOOL "Whether CUDA-capable GPU is present")
else()
set(CUDA_HAVE_GPU FALSE CACHE BOOL "Whether CUDA-capable GPU is present")
endif()
endif(CUDA_FOUND)
هذا يحدد أ CUDA_HAVE_GPU
متغير منطقي في CMake والذي يمكن استخدامه لاحقًا لإثارة العمليات المشروطة.
استغرق الأمر مني وقتًا طويلاً لمعرفة أن معلمات التضمين والربط تحتاج إلى الذهاب في CMAKE_FLAGS Stanza ، وما ينبغي أن يكون بناء الجملة. ال توثيق try_run خفيفة للغاية ، ولكن هناك المزيد من المعلومات في توثيق try_compile, ، وهو أمر مرتبط ارتباطا وثيقا. ما زلت بحاجة إلى البحث عن الويب للحصول على أمثلة من try_compile و try_run قبل الحصول على هذا العمل.
التفاصيل الأخرى الصعبة ولكن المهمة هي الحجة الثالثة try_run
, ، "bindir". ربما يجب عليك دائمًا ضبط هذا على ${CMAKE_BINARY_DIR}
. على وجه الخصوص ، لا تقم بتعيينها على ${CMAKE_CURRENT_BINARY_DIR}
إذا كنت في دليل فرعي لمشروعك. يتوقع Cmake العثور على الدليل الفرعي CMakeFiles/CMakeTmp
ضمن Bindir ، وينبث أخطاء إذا لم يكن هذا الدليل موجودًا. فقط استخدم ${CMAKE_BINARY_DIR}
, ، وهو مكان واحد يبدو فيه أن هذه الدلالات الفرعية يقيمون بشكل طبيعي.
نصائح أخرى
اكتب برنامجًا بسيطًا مثل
#include<cuda.h>
int main (){
int deviceCount;
cudaError_t e = cudaGetDeviceCount(&deviceCount);
return e == cudaSuccess ? deviceCount : -1;
}
وتحقق من قيمة الإرجاع.
لقد كتبت للتو نصًا نقيًا للبيثون يقوم ببعض الأشياء التي يبدو أنك تحتاجها (أخذت الكثير من هذا من مشروع Pystream). إنه في الأساس مجرد غلاف لبعض الوظائف في مكتبة وقت تشغيل CUDA (يستخدم ctypes). انظر إلى الوظيفة الرئيسية () لرؤية الاستخدام على سبيل المثال. أيضًا ، كن على علم بأنني كتبت للتو ، لذلك من المحتمل أن تحتوي على الأخطاء. استخدم بحذر.
#!/bin/bash
import sys
import platform
import ctypes
"""
cudart.py: used to access pars of the CUDA runtime library.
Most of this code was lifted from the pystream project (it's BSD licensed):
http://code.google.com/p/pystream
Note that this is likely to only work with CUDA 2.3
To extend to other versions, you may need to edit the DeviceProp Class
"""
cudaSuccess = 0
errorDict = {
1: 'MissingConfigurationError',
2: 'MemoryAllocationError',
3: 'InitializationError',
4: 'LaunchFailureError',
5: 'PriorLaunchFailureError',
6: 'LaunchTimeoutError',
7: 'LaunchOutOfResourcesError',
8: 'InvalidDeviceFunctionError',
9: 'InvalidConfigurationError',
10: 'InvalidDeviceError',
11: 'InvalidValueError',
12: 'InvalidPitchValueError',
13: 'InvalidSymbolError',
14: 'MapBufferObjectFailedError',
15: 'UnmapBufferObjectFailedError',
16: 'InvalidHostPointerError',
17: 'InvalidDevicePointerError',
18: 'InvalidTextureError',
19: 'InvalidTextureBindingError',
20: 'InvalidChannelDescriptorError',
21: 'InvalidMemcpyDirectionError',
22: 'AddressOfConstantError',
23: 'TextureFetchFailedError',
24: 'TextureNotBoundError',
25: 'SynchronizationError',
26: 'InvalidFilterSettingError',
27: 'InvalidNormSettingError',
28: 'MixedDeviceExecutionError',
29: 'CudartUnloadingError',
30: 'UnknownError',
31: 'NotYetImplementedError',
32: 'MemoryValueTooLargeError',
33: 'InvalidResourceHandleError',
34: 'NotReadyError',
0x7f: 'StartupFailureError',
10000: 'ApiFailureBaseError'}
try:
if platform.system() == "Microsoft":
_libcudart = ctypes.windll.LoadLibrary('cudart.dll')
elif platform.system()=="Darwin":
_libcudart = ctypes.cdll.LoadLibrary('libcudart.dylib')
else:
_libcudart = ctypes.cdll.LoadLibrary('libcudart.so')
_libcudart_error = None
except OSError, e:
_libcudart_error = e
_libcudart = None
def _checkCudaStatus(status):
if status != cudaSuccess:
eClassString = errorDict[status]
# Get the class by name from the top level of this module
eClass = globals()[eClassString]
raise eClass()
def _checkDeviceNumber(device):
assert isinstance(device, int), "device number must be an int"
assert device >= 0, "device number must be greater than 0"
assert device < 2**8-1, "device number must be < 255"
# cudaDeviceProp
class DeviceProp(ctypes.Structure):
_fields_ = [
("name", 256*ctypes.c_char), # < ASCII string identifying device
("totalGlobalMem", ctypes.c_size_t), # < Global memory available on device in bytes
("sharedMemPerBlock", ctypes.c_size_t), # < Shared memory available per block in bytes
("regsPerBlock", ctypes.c_int), # < 32-bit registers available per block
("warpSize", ctypes.c_int), # < Warp size in threads
("memPitch", ctypes.c_size_t), # < Maximum pitch in bytes allowed by memory copies
("maxThreadsPerBlock", ctypes.c_int), # < Maximum number of threads per block
("maxThreadsDim", 3*ctypes.c_int), # < Maximum size of each dimension of a block
("maxGridSize", 3*ctypes.c_int), # < Maximum size of each dimension of a grid
("clockRate", ctypes.c_int), # < Clock frequency in kilohertz
("totalConstMem", ctypes.c_size_t), # < Constant memory available on device in bytes
("major", ctypes.c_int), # < Major compute capability
("minor", ctypes.c_int), # < Minor compute capability
("textureAlignment", ctypes.c_size_t), # < Alignment requirement for textures
("deviceOverlap", ctypes.c_int), # < Device can concurrently copy memory and execute a kernel
("multiProcessorCount", ctypes.c_int), # < Number of multiprocessors on device
("kernelExecTimeoutEnabled", ctypes.c_int), # < Specified whether there is a run time limit on kernels
("integrated", ctypes.c_int), # < Device is integrated as opposed to discrete
("canMapHostMemory", ctypes.c_int), # < Device can map host memory with cudaHostAlloc/cudaHostGetDevicePointer
("computeMode", ctypes.c_int), # < Compute mode (See ::cudaComputeMode)
("__cudaReserved", 36*ctypes.c_int),
]
def __str__(self):
return """NVidia GPU Specifications:
Name: %s
Total global mem: %i
Shared mem per block: %i
Registers per block: %i
Warp size: %i
Mem pitch: %i
Max threads per block: %i
Max treads dim: (%i, %i, %i)
Max grid size: (%i, %i, %i)
Total const mem: %i
Compute capability: %i.%i
Clock Rate (GHz): %f
Texture alignment: %i
""" % (self.name, self.totalGlobalMem, self.sharedMemPerBlock,
self.regsPerBlock, self.warpSize, self.memPitch,
self.maxThreadsPerBlock,
self.maxThreadsDim[0], self.maxThreadsDim[1], self.maxThreadsDim[2],
self.maxGridSize[0], self.maxGridSize[1], self.maxGridSize[2],
self.totalConstMem, self.major, self.minor,
float(self.clockRate)/1.0e6, self.textureAlignment)
def cudaGetDeviceCount():
if _libcudart is None: return 0
deviceCount = ctypes.c_int()
status = _libcudart.cudaGetDeviceCount(ctypes.byref(deviceCount))
_checkCudaStatus(status)
return deviceCount.value
def getDeviceProperties(device):
if _libcudart is None: return None
_checkDeviceNumber(device)
props = DeviceProp()
status = _libcudart.cudaGetDeviceProperties(ctypes.byref(props), device)
_checkCudaStatus(status)
return props
def getDriverVersion():
if _libcudart is None: return None
version = ctypes.c_int()
_libcudart.cudaDriverGetVersion(ctypes.byref(version))
v = "%d.%d" % (version.value//1000,
version.value%100)
return v
def getRuntimeVersion():
if _libcudart is None: return None
version = ctypes.c_int()
_libcudart.cudaRuntimeGetVersion(ctypes.byref(version))
v = "%d.%d" % (version.value//1000,
version.value%100)
return v
def getGpuCount():
count=0
for ii in range(cudaGetDeviceCount()):
props = getDeviceProperties(ii)
if props.major!=9999: count+=1
return count
def getLoadError():
return _libcudart_error
version = getDriverVersion()
if version is not None and not version.startswith('2.3'):
sys.stdout.write("WARNING: Driver version %s may not work with %s\n" %
(version, sys.argv[0]))
version = getRuntimeVersion()
if version is not None and not version.startswith('2.3'):
sys.stdout.write("WARNING: Runtime version %s may not work with %s\n" %
(version, sys.argv[0]))
def main():
sys.stdout.write("Driver version: %s\n" % getDriverVersion())
sys.stdout.write("Runtime version: %s\n" % getRuntimeVersion())
nn = cudaGetDeviceCount()
sys.stdout.write("Device count: %s\n" % nn)
for ii in range(nn):
props = getDeviceProperties(ii)
sys.stdout.write("\nDevice %d:\n" % ii)
#sys.stdout.write("%s" % props)
for f_name, f_type in props._fields_:
attr = props.__getattribute__(f_name)
sys.stdout.write( " %s: %s\n" % (f_name, attr))
gpuCount = getGpuCount()
if gpuCount > 0:
sys.stdout.write("\n")
sys.stdout.write("GPU count: %d\n" % getGpuCount())
e = getLoadError()
if e is not None:
sys.stdout.write("There was an error loading a library:\n%s\n\n" % e)
if __name__=="__main__":
main()
يمكنك تجميع برنامج استعلام GPU الصغير إذا تم العثور على CUDA. إليك واحدة بسيطة يمكنك تبني الاحتياجات:
#include <stdlib.h>
#include <stdio.h>
#include <cuda.h>
#include <cuda_runtime.h>
int main(int argc, char** argv) {
int ct,dev;
cudaError_t code;
struct cudaDeviceProp prop;
cudaGetDeviceCount(&ct);
code = cudaGetLastError();
if(code) printf("%s\n", cudaGetErrorString(code));
if(ct == 0) {
printf("Cuda device not found.\n");
exit(0);
}
printf("Found %i Cuda device(s).\n",ct);
for (dev = 0; dev < ct; ++dev) {
printf("Cuda device %i\n", dev);
cudaGetDeviceProperties(&prop,dev);
printf("\tname : %s\n", prop.name);
printf("\ttotalGlobablMem: %lu\n", (unsigned long)prop.totalGlobalMem);
printf("\tsharedMemPerBlock: %i\n", prop.sharedMemPerBlock);
printf("\tregsPerBlock: %i\n", prop.regsPerBlock);
printf("\twarpSize: %i\n", prop.warpSize);
printf("\tmemPitch: %i\n", prop.memPitch);
printf("\tmaxThreadsPerBlock: %i\n", prop.maxThreadsPerBlock);
printf("\tmaxThreadsDim: %i, %i, %i\n", prop.maxThreadsDim[0], prop.maxThreadsDim[1], prop.maxThreadsDim[2]);
printf("\tmaxGridSize: %i, %i, %i\n", prop.maxGridSize[0], prop.maxGridSize[1], prop.maxGridSize[2]);
printf("\tclockRate: %i\n", prop.clockRate);
printf("\ttotalConstMem: %i\n", prop.totalConstMem);
printf("\tmajor: %i\n", prop.major);
printf("\tminor: %i\n", prop.minor);
printf("\ttextureAlignment: %i\n", prop.textureAlignment);
printf("\tdeviceOverlap: %i\n", prop.deviceOverlap);
printf("\tmultiProcessorCount: %i\n", prop.multiProcessorCount);
}
}
تتمثل إحدى الطرق المفيدة في تشغيل البرامج التي قامت بها CUDA ، مثل Nvidia-SMI ، لمعرفة ما يعودون.
find_program(_nvidia_smi "nvidia-smi") if (_nvidia_smi) set(DETECT_GPU_COUNT_NVIDIA_SMI 0) # execute nvidia-smi -L to get a short list of GPUs available exec_program(${_nvidia_smi_path} ARGS -L OUTPUT_VARIABLE _nvidia_smi_out RETURN_VALUE _nvidia_smi_ret) # process the stdout of nvidia-smi if (_nvidia_smi_ret EQUAL 0) # convert string with newlines to list of strings string(REGEX REPLACE "\n" ";" _nvidia_smi_out "${_nvidia_smi_out}") foreach(_line ${_nvidia_smi_out}) if (_line MATCHES "^GPU [0-9]+:") math(EXPR DETECT_GPU_COUNT_NVIDIA_SMI "${DETECT_GPU_COUNT_NVIDIA_SMI}+1") # the UUID is not very useful for the user, remove it string(REGEX REPLACE " \\(UUID:.*\\)" "" _gpu_info "${_line}") if (NOT _gpu_info STREQUAL "") list(APPEND DETECT_GPU_INFO "${_gpu_info}") endif() endif() endforeach() check_num_gpu_info(${DETECT_GPU_COUNT_NVIDIA_SMI} DETECT_GPU_INFO) set(DETECT_GPU_COUNT ${DETECT_GPU_COUNT_NVIDIA_SMI}) endif() endif()
يمكن للمرء أيضًا الاستعلام عن Linux /Proc أو LSPCI. انظر مثال Cmake الذي يعمل بالكامل في https://github.com/gromacs/gromacs/blob/master/cmake/gmxdetectgpu.cmake