本系列为英伟达GPU入门介绍的第二篇,主要介绍CUDA编程的基本流程和核心概念,并使用Python Numba编写GPU并行程序。为了更好地理解GPU的硬件架构,建议读者先阅读我的第一篇文章。
-
GPU硬件知识和基础概念:包括CPU与GPU的区别、GPU架构、CUDA软件栈简介。
-
GPU编程入门:主要介绍CUDA核函数,Thread、Block和Grid概念,并使用Python Numba进行简单的并行计算。
-
GPU编程进阶:主要介绍一些优化方法。
-
GPU编程实践:使用Python Numba解决复杂问题。
针对Python的CUDA教程
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)相互通信。
在进行GPU编程前,需要先确认是否安装了CUDA工具箱,可以使用echo $CUDA_HOME
检查CUDA环境变量,返回值不为空说明已经安装好CUDA。也可以直接用Anaconda里的conda
命令安装CUDA:
$ conda install cudatoolkit
然后可以使用nvidia-smi
命令查看显卡情况,比如这台机器上几张显卡,CUDA版本,显卡上运行的进程等。我这里是一台32GB显存版的Telsa V100机器。
安装Numba库:
$ conda install numba
检查一下CUDA和Numba是否安装成功:
from numba import cuda
print(cuda.gpus)
如果上述步骤没有问题,可以得到结果:<Managed Device 0>...
。如果机器上没有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=1
Windows:
SET NUMBA_ENABLE_CUDASIM=1
需要注意的是,模拟器只是一个调试的工具,在模拟器中使用Numba并不能加速程序,有可能速度更慢,而且在模拟器能够运行的程序,并不能保证一定能在真正的GPU上运行,最终还是要以GPU为准。
有了以上的准备工作,我们就可以开始我们的GPU编程之旅了!
GPU程序与CPU程序的区别
一个传统的CPU程序的执行顺序如下图所示:
CPU程序是顺序执行的,一般需要:
-
初始化。
-
CPU计算。
-
得到计算结果。
在CUDA编程中,CPU和主存被称为主机(Host),GPU被称为设备(Device)。
当引入GPU后,计算流程变为:
- 初始化,并将必要的数据拷贝到GPU设备的显存上。
- CPU调用GPU函数,启动GPU多个核心同时进行计算。
- CPU与GPU异步计算。
- 将GPU计算结果拷贝回主机端,得到计算结果。
一个名为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代码不同的是:<