3.2_设备与初始化
3.2 设备与初始化
设备就是指真实的GPU实体。当CUDA初始化完成(显式调用驱动程序接口cuInit()或隐式调用CUDA运行时函数),CUDA驱动程序将枚举可用的设备,并创建一个全局数据结构体,这个结构体包括设备名称和一些不可变的参数,例如设备内存数量和最大时钟速率等。
在一些平台上,英伟达包含设置特定设备的工具。nvidia-smi工具可设置GPU的策略,例如,它可以启用或禁用ECC(纠错),它也可以用来控制设备创建的CUDA上下文数量。下面是一些可能的模式:
·默认:多个CUDA上下文可被设备创建。
·独占模式:只有一个CUDA上下文可被设备创建。
禁止模式:禁止设备创建CUDA上下文。
如果一个设备已被列出,但无法在其上创建上下文,那么设备可能是处于禁止模式或者独占模式中,对于后者,说明已有一份CUDA上下文被创建。
3.2.1 设备数量
应用程序可以通过调用cuDeviceGetCount()或CUDAGetDeviceCount()函数查询有多少设备可用。之后,设备可以通过一个来自范围[0..DeviceCount-1]的索引来引用它。驱动程序API需要应用程序调用cuDeviceGet()来映射设备的索引到一个设备句柄(CUdevice)上。
3.2.2 设备属性
驱动程序API应用程序可以通过调用cuDeviceGetName()来查询设备名称,调用cuDeviceTotalMem()查询全局内存。设备的主要与次要计算能力(即SM版本,例如第一代费米架构GPU的计算能力为2.0)可以使用cuDeviceComputeCapability()函数查询。
CUDA运行时应用程序可以调用CUDAGetDeviceProperties(), 返回包括设备名称与属性信息的结构体CUDADeviceProp, 表3-2给出了结构体中成员的描述。
CUDA驱动程序API接口函数cuDeviceGetAttribute()可以查询设备属性,根据CUdevice_attribute参数一次返回一个属性。CUDA 5.0中,CUDA运行时添加了相同的功能函数:
CUDADeviceGetAttribute(), 想必因为基于结构体的接口在设备上运行时很笨重。
表3-2 CUDADeviceProp成员
(续)
(续)
3.2.3 无CUDA支持情况
CUDA运行时程序可以运行在不能运行CUDA的机器或未安装CUDA的机器上。如果CUDAGetDeviceCount()函数返回CUDASuccess和一个非0的设备数,CUDA就是可用的。
与CUDA运行时相反,当使用驱动程序API时,除非驱动程序二进制文件是可用的,否则直接链接nvcuda.dll(Windows)或libcuda.so(Linux)的可执行文件便不会成功加载。正因为驱动程序API需要CUDA,所以当CUDA不存在时,直接启动程序会引发图3-3中的错误。

图3-3 当CUDA不存在时的错误对话框(Windows)
对于CUDA的应用程序,CUDA SDK提供了一组头文件和一个C源文件,包装了驱动程序API,这样,应用程序便可以检查CUDA而避免操作系统发出异常信号。这些文件,存放在子目录/C/common/inc/dynlink下,可以代替CUDA的核心文件。它们会插入一组中间函数,可以在CUDA可用时延迟加载CUDA库。
作为例子,让我们比较两个小程序。它们使用驱动程序API初始化CUDA并输出系统中的设备名字。代码清单3-1给出了init_hardware.cpp文件的代码,通过下面的命令可以使用CUDA SDK编译这个文件:
使用nvcc编译C++文件,不包含任何的GPU代码,是一种很方便的获取CUDA头文件的方式。在这行命令的开始,-oinit_hardware确定了输出的可执行文件的根名称。命令最后的-lcuda使nvcc链接驱动程序API库,缺少它,会引发一个链接错误。这个程序硬链接(hard-link)CUDA驱动程序API,所以在未安装CUDA的机器上会编译失败。
代码清单3-1 初始化(硬编码)
$\begin{array}{l}\text{串}\\ \text{*init\_hardcoded.cpp}\\ \text{*}\\ \text{串}\\ \# include < stdio.h>\\ \# include < CUDA.h>\\ \# include <chError.h>\\ \text{int}\\ \text{main()}\\ \{\end{array}$ Cresult status; int numDevices; CUDA_CHECK( cuInit( 0 ) ); CUDA_CHECK( cuDeviceGetCount ( &numDevices ) ); printf("%d devices detected:\\n", numDevices); for ( int i = 0; i < numDevices; i++) { char szName[256]; CUdevice device; CUDA_CHECK( cuDeviceGet( &device, i ) ); CUDA_CHECK( cuDeviceGetName( szName, 255, device ) ); printf( "\t%s\n", szName ); } return 0; Error: fprintf( stderr, "CUDA failure code: 0x%x\n", status ); return 1;代码清单3-2给出的程序不需要系统中装有CUDA。可以看到,源代码几乎是相同的,仅有几行差别。其中,
include <cuda.h>被替换为:
include "cuda_drvdynlink.c" #include "dynlink/cuda_drvdynlink.h"而cnInit()函数调用改变为指定一个CUDA版本。即,
CUDA_CHECK{cuInit(0)};被替换为:
CUDA_CHECK{cuInit(0,4010));这里我们为CUDA_CHECK传入了第二个参数4010,代表CUDA 4.1版本,如果系统不包括4.1版本的函数库,程序会编译失败。
注意你可以单独编译然后链接cuda_drvdynlink.c到你的应用程序里,代替直接‘include’这个文件到源代码中。头文件和C文件一同工作,在驱动程序API中插入一组包装好的函数。头文件使用预处理器重新命名驱动程序API函数以包装在cuda_drvdynlink.h头文件中声明的函数(例如调用cuCtxCreate()会实际调用tcuCtxCreate())。在支持CUDA的系统中,驱动程序动态链接库是动态加载的,包装好的函数会调用在初始化时从驱动程序动态链接库获取的函数指针;在没有CUDA的系统中,或者驱动程序不匹配请求的CUDA版本,初始化函数会返回一个错误。
代码清单3-2 初始化(dynlink)
/\* init_dynlink.cpp \*/
#include<stdio.h>
#include "dynlink/cuda_drvapi_dynlink.h"
#include <chError.h>
int
main()
{ CUresult status; int numDevices; CUDA_CHECK( cuInit(0,4010)); CUDA_CHECK( cuDeviceGetCount( &numDevices)); printf("%d devices detected:\\n", numDevices); for (int i = 0; i < numDevices; i++) { char szName[256]; CUdevice device; CUDA_CHECK( cuDeviceGet( &device,i)) ; CUDA_CHECK( cuDeviceGetName( szName,255,device) ); printf("\t%s\n",szName); } return 0; Error: fprintf(stderr, "CUDA failure code: $0\mathrm{x}\& \mathrm{x}\backslash \mathrm{n}^{\prime \prime}$ ,status); return 1;CUDA专用动态链接库(CUDA-Only DLLs)
对Windows开发者,另一个在不支持CUDA的平台上创建CUDA应用程序的方式如下:
1)把CUDA代码转移到动态链接库。
(2) 调用loadLibrary()显式加载动态链接库。
3)在__try/__except语句中调用LoadLibrary()以捕捉CUDA不存在情况下的异常。