中文领域最详细的Python版CUDA入门教程 |
您所在的位置:网站首页 › python编程在哪里写 › 中文领域最详细的Python版CUDA入门教程 |
本系列为英伟达GPU入门介绍的第二篇,主要介绍CUDA编程的基本流程和核心概念,并使用Python Numba编写GPU并行程序。为了更好地理解GPU的硬件架构,建议读者先阅读我的第一篇文章。 GPU硬件知识和基础概念:包括CPU与GPU的区别、GPU架构、CUDA软件栈简介。 GPU编程入门:主要介绍CUDA核函数,Thread、Block和Grid概念,并使用Python Numba进行简单的并行计算。 GPU编程进阶:主要介绍一些优化方法。 GPU编程实践:使用Python Numba解决复杂问题。 Python是当前最流行的编程语言,被广泛应用在深度学习、金融建模、科学和工程计算上。作为一门解释型语言,它运行速度慢也常常被用户诟病。著名Python发行商Anaconda公司开发的Numba库为程序员提供了Python版CPU和GPU编程工具,速度比原生Python快数十倍甚至更多。使用Numba进行GPU编程,你可以享受: Python简单易用的语法;极快的开发速度;成倍的硬件加速。为了既保证Python语言的易用性和开发速度,又达到并行加速的目的,本系列主要从Python的角度给大家分享GPU编程方法。关于Numba的入门可以参考我的另一篇文章。更加令人兴奋的是,Numba提供了一个GPU模拟器,即使你手头暂时没有GPU机器,也可以先使用这个模拟器来学习GPU编程! 初识GPU编程兵马未动,粮草先行。在开始GPU编程前,需要明确一些概念,并准备好相关工具。 CUDA是英伟达提供给开发者的一个GPU编程框架,程序员可以使用这个框架轻松地编写并行程序。本系列第一篇文章提到,CPU和主存被称为主机(Host),GPU和显存(显卡内存)被称为设备(Device),CPU无法直接读取显存数据,GPU无法直接读取主存数据,主机与设备必须通过总线(Bus)相互通信。 然后可以使用nvidia-smi命令查看显卡情况,比如这台机器上几张显卡,CUDA版本,显卡上运行的进程等。我这里是一台32GB显存版的Telsa V100机器。 检查一下CUDA和Numba是否安装成功: from numba import cuda print(cuda.gpus)如果上述步骤没有问题,可以得到结果:...。如果机器上没有GPU或没安装好上述包,会有报错。CUDA程序执行时会独霸一张卡,如果你的机器上有多张GPU卡,CUDA默认会选用0号卡。如果你与其他人共用这台机器,最好协商好谁在用哪张卡。一般使用CUDA_VISIBLE_DEVICES这个环境变量来选择某张卡。如选择5号GPU卡运行你的程序。 CUDA_VISIBLE_DEVICES='5' python example.py如果手头暂时没有GPU设备,Numba提供了一个模拟器,供用户学习和调试,只需要在命令行里添加一个环境变量。 Mac/Linux: export NUMBA_ENABLE_CUDASIM=1Windows: SET NUMBA_ENABLE_CUDASIM=1需要注意的是,模拟器只是一个调试的工具,在模拟器中使用Numba并不能加速程序,有可能速度更慢,而且在模拟器能够运行的程序,并不能保证一定能在真正的GPU上运行,最终还是要以GPU为准。 有了以上的准备工作,我们就可以开始我们的GPU编程之旅了! GPU程序与CPU程序的区别一个传统的CPU程序的执行顺序如下图所示: 初始化。 CPU计算。 得到计算结果。 在CUDA编程中,CPU和主存被称为主机(Host),GPU被称为设备(Device)。 一个名为gpu_print.py的GPU程序如下所示: from numba import cuda def cpu_print(): print("print by cpu.") @cuda.jit def gpu_print(): # GPU核函数 print("print by gpu.") def main(): gpu_print[1, 2]() cuda.synchronize() cpu_print() if __name__ == "__main__": main()使用CUDA_VISIBLE_DEVICES='0' python gpu_print.py执行这段代码,得到的结果为: print by gpu. print by gpu. print by cpu.与传统的Python CPU代码不同的是: 使用from numba import cuda引入cuda库在GPU函数上添加@cuda.jit装饰符,表示该函数是一个在GPU设备上运行的函数,GPU函数又被称为核函数。主函数调用GPU核函数时,需要添加如[1, 2]这样的执行配置,这个配置是在告知GPU以多大的并行粒度同时进行计算。gpu_print[1, 2]()表示同时开启2个线程并行地执行gpu_print函数,函数将被并行地执行2次。下文会深入探讨如何设置执行配置。GPU核函数的启动方式是异步的:启动GPU函数后,CPU不会等待GPU函数执行完毕才执行下一行代码。必要时,需要调用cuda.synchronize(),告知CPU等待GPU执行完核函数后,再进行CPU端后续计算。这个过程被称为同步,也就是GPU执行流程图中的红线部分。如果不调用cuda.synchronize()函数,执行结果也将改变,"print by cpu.将先被打印。虽然GPU函数在前,但是程序并没有等待GPU函数执行完,而是继续执行后面的cpu_print函数,由于CPU调用GPU有一定的延迟,反而后面的cpu_print先被执行,因此cpu_print的结果先被打印了出来。 Thread层次结构前面的程序中,核函数被GPU并行地执行了2次。在进行GPU并行编程时需要定义执行配置来告知以怎样的方式去并行计算,比如上面打印的例子中,是并行地执行2次,还是8次,还是并行地执行20万次,或者2000万次。2000万的数字太大,远远多于GPU的核心数,如何将2000万次计算合理分配到所有GPU核心上。解决这些问题就需要弄明白CUDA的Thread层次结构。 实际上,线程(thread)是一个编程上的软件概念。从硬件来看,thread运行在一个CUDA核心上,多个thread组成的block运行在Streaming Multiprocessor(SM的概念详见本系列第一篇文章),多个block组成的grid运行在一个GPU显卡上。
利用上述变量,我们可以把之前的代码丰富一下: from numba import cuda def cpu_print(N): for i in range(0, N): print(i) @cuda.jit def gpu_print(N): idx = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x if (idx |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |