统一CUDA Python生态系统

2021-04-16 23:40:19

Python在科学,工程,数据分析和深度学习应用生态系统中发挥着关键作用。 NVIDIA长期以来一直致力于帮助Python生态系统利用GPU的加速大规模平行性能,以提供标准化的库,工具和应用。如今,我们正在向简化开发人员体验简化改进的Python代码可移植性和兼容性。

我们的目标是帮助统一Python CUDA生态系统,单一标准的低级接口,提供从Python的CUDA主机API的完全覆盖和访问。我们希望提供生态系统基础,以允许不同加速库之间的互操作性。最重要的是,Python开发人员应该容易使用NVIDIA GPU。

迄今为止,可以通过Python访问CUDA和NVIDIA GPU,只能通过NumBA,Cupy,Scikit-Cuda,Rapids,Pycuda,Pytorch或Tensorflow等第三方软件来完成,只是为了命名几个。每个都在CUDA API和Python之间写了自己的互操作性层。

通过释放CUDA Python,NVIDIA使这些平台提供商能够专注于自己的增值产品和服务。 NVIDIA还希望降低其他Python开发人员使用NVIDIA GPU的障碍。 CUDA Python的初始版本包括CUDA驱动程序和运行时API的Cython和Python包装器。

在未来的版本中,我们可以为CUDA库(Cublas,Cufft,Cudnn,NVJPEG等)提供Pythonic对象模型和包装器。即将推出的版本也可以在GitHub上的源代码或通过PIP和公共区域进行包装。

由于Python是一种解释语言,所以您需要一种方法来将设备代码编译为PTX,然后提取要在应用程序的稍后点调用的函数。对于理解CUDA Python并不重要,但并行线程执行(PTX)是一个低级虚拟机和指令集架构(ISA)。您以字符串的形式构建您的设备代码,并使用NVRTC编译它,这是CUDA C ++的运行时编译库。使用NVIDIA驱动程序API,手动创建CUDA上下文以及GPU上的所有所需资源,然后启动编译的CUDA C ++代码并从GPU检索结果。既然您概述了,跳入一个常用的并行编程示例:saxpy。

首先要做的是从CUDA Python包导入驱动程序API和NVRTC模块。在此示例中,您将数据从主机复制到设备。您需要numpy来存储主机上的数据。

将CUDA_DRIVER导入CUDA#主题在HeledimIMPORT NVRTC#之前进行更改,以便在HeledimImport Numpy作为NP之前更改

错误检查是代码开发中的基本最佳实践,并提供代码示例。对于简洁起见,省略了示例中的错误检查。在将来的释放中,这可能会使用Python对象模型自动提出异常。

def assert_drv(err):如果isinstance(err,cuda.curesult):如果err!= cuda.curesult.cuda_success:raintrationError(" cuda错误:{}" .format(err))elif isinstance (err,nvrtc.nvrtcresult):如果err!= nvrtc.nvrtcresult.nvrtc_success:ring timerror(" nvrtc错误:{}" .format(err))else:race timerror("未知错误类型:{}" .format(err))

在翻译单元顶部附近写CUDA内核是常见的做法,所以接下来写它。整个内核以三重引号包裹以形成一个字符串。稍后使用NVRTC编译字符串。这是CUDA Python的唯一一部分,需要一些对CUDA C ++的理解。有关更多信息,请参阅甚至更轻松地介绍CUDA。

Saxpy =""" \ extern" c" __Grobal__void saxpy(float a,float * x,float * y,float * out,size_t n){size_t tid = blockidx.x * blockdim.x + threadidx.x; if(tid< n){out [tid] = a * x [tid] + y [tid]; }}}}}"""

继续编译内核进入PTX。请记住,使用NVRTC在运行时执行。 NVRTC有三个基本步骤:

在以下代码示例中,编译是针对Compute能力75或TING架构,使用FMAD启用。如果编译失败,请使用nvrtcgetproglog检索编译日志以获取其他信息。

#创建程序交弹,prog = nvrtc.nvrtccreateprogram(str.encode(saxpy),b" saxpy.cu&#34 ;, 0,[],[])#编译程序= [b" - fmad = false #34;,B" - gpu-architecture = compute_75"] err,= nvrtc.nvrtccompileprogram(prog,2,opts)#get ptx from compilationerr,ptxsize = nvrtc.nvrtcgettxsize(prog)ptx = b&# 34; " * ptxsizeerr,= nvrtc.nvrtcgetptx(prog,ptx)

在使用PTX或在GPU上执行任何工作之前,必须创建CUDA上下文。 CUDA上下文类似于设备的主机进程。在以下代码示例中,初始化驱动程序API,以便可访问NVIDIA驱动程序和GPU。接下来,将计算设备0的句柄传递给CuctXCreate以指定用于上下文创建的GPU。在创建的上下文中,您可以使用NVRTC进行编译CUDA内核。

#initialize cuda driver apierr,= cuda.cuinit(0)#检索设备0err,cudevice = cuda.cudeviceget(0)#创建contexterr,context = cuda.cuctxcreate(0,cudevice)

使用在设备0上创建的CUDA上下文,将前面生成的PTX加载到模块中。模块类似于动态加载设备的库。加载到模块后,用CufoduleGetFunction提取特定内核。多个内核驻留在PTX中并不罕见。

#加载ptx作为模块数据和检索函数portsptx = np.char.array(ptx)err,module = cuda.cumoduleloaddata(ptx.ctypes.get_data())err,kernel = cuda.cumodulegetFunction(模块,B" Saxpy&# 34;)

接下来,获取准备和转移到GPU的所有数据。为了提高应用程序性能,您可以输入设备上的数据以消除数据传输。为了完整性,此示例显示了如何将数据传输到设备。

num_threads = 512#每个blocknum_blocks的线程= 32768#每个grida = np.array([2.0],dtype = np.float32)n = np.array(num_threads * num_blocks,dtype = np.uint32)buffersize = n * a。项目zhx = np.random.rand(n).astype(dtype = np.float32)hy = np.random.rand(n).astype(dtype = np.float32)hout = np.zeros(n).astype(dtype = np.float32)

对于为SAXPY变换设备创建的输入数据A,X和Y,必须分配资源以使用CUMEMALLOC存储数据。要允许在计算和数据移动之间进行更多重叠,请使用异步函数cumemcpyhtodasync。在命令执行之后,它立即将控件返回到CPU。

Python没有指针的自然概念,但CumemcPyHtodasync预计将失效*。因此,xx.ctypes.get_data检索与xx关联的指针值。

err,dxclass = cuda.cumalaloc(缓冲区大小)err,dyclass = cuda.cumemalloc(缓冲)err,doutclass = cuda.cumemalloc(缓冲)err,stream = cuda.custeamcreate(0)err,= cuda.cumemcyhtodasync(dxclass,hx .ctypes.get_data(),buffersize,stream)err,= cuda.cumemcpyhtodasync(dyclass,hy.ctypes.get_data(),缓冲,流)

通过数据准备和资源分配完成,内核已准备就绪。要将数据的位置传递到内核执行配置,必须检索设备指针。在以下代码示例中,int(dxclass)重试dxclass的指针值,该指针值是cudeviceptr,并为存储器大小分配以使用np.array存储此值。

像CumemcPyhtodasync一样,Culaunchkernel期望Void **参数列表中。在早期的代码示例中,它通过抓住每个单独参数的void *值并将它们放入自己的连续内存来创建void **。

#以下代码示例不是直观的#,可以在未来的重新启动= np.Array中更改([int(dxclass)],dtype = np.uint64)dy = np.array([int(dyclass)],dtype = np .uint64)dout = np.array([int(doutclass)],dtype = np.uint64)args = [a,dx,dy,dout,n] args = np.array([arg.ctypes.get_data() args],dtype = np.uint64)

err,= cuda.culaunchkernel(内核,num_blocks,#grid x dim 1,#grid y dim 1,#grid z昏暗num_threads,#block x dim 1,#block y dim 1,#block z dim 0,#动态共享内存流,#stream args.ctypes.get_data(),#内核参数0,#extra(忽略))err,= cuda.cumemcpydtohasync(hout.ctypes.get_data(),doutclass,buffersize,stream)err,= cuda。 custreamsynchronize(Stream)

CULUUNNKERNEL函数采用已编译的模块内核和执行配置参数。设备代码在与数据传输相同的流中启动。这可确保仅在数据完成传输后执行内核的计算,因为所有API调用和在流中启动都会序列化。执行呼叫转回主机后,CuStreamSynchronize用于停止CPU执行,直到指定流中的所有操作完成。

#sssert值在运行kernelhz = a * hx + hyif之后也是相同的,而不是np.allclose(hout,hz):提升valuseerror("主机设备向量的公差超出误差;)

执行数据验证以确保正确性,并使用内存清理完成代码。

性能是针对您的应用程序中的GPU中的主要驱动程序。那么,上面的代码如何与其C ++版本进行比较?表1显示结果几乎相同。 NVIDIA NSIGHT系统用于检索内核性能,CUDA事件用于应用程序性能。

CUDA Python也与NVIDIA NSIGHT Compute兼容,这是一个用于CUDA应用程序的交互式内核分析器。它允许您对内核性能进行详细的洞察。当您尝试最大化性能时,这很有用(图1)。

CUDA Python即将推出,以及API,安装说明,新功能和示例的详细说明。有关更多信息,请参阅以下帖子:

特别感谢CUDA Python开发人员Vladislav Zhurba,他的帮助在这篇文章中提供的例子。