教你GPU编程,NVIDIA Python CUDA Numba 大法好

0 前言

2018年暑假7月开始,我跟着一个学长做项目。学长负责理论和算法,我负责编程实现和做实验。Python程序写出来了,但是很慢。Python的for loop真是龟速呀。这个程序的瓶颈部分,就是一个双层for loop,内层for loop里是矩阵乘法。于是乎想到了numba来给瓶颈部分做优化。简单的@numba.jit可以加速几十倍,但是很奇怪无法和joblib配合使用。

最终解决方案是使用@numba.cuda.jit,他可以轻松加速三千多倍 — 这片博客就带你入门GPU编程,本文出了阐述我对于GPU编程的理解和小结,还引用了一些非常好的学习资料。我这里说的GPU,专门指的是NVIDIA GPU的CUDA编程。


1 GPU 编程思想

传统意义上来讲,大部分程序是运行在CPU上的,GPU只是玩游戏的时候派上用场。然而现在GPU的重要性大幅度提升,NVIDIA的股票也是高速上涨,因为GPU除了游戏,增加了一个killer app:机器学习。

编写CPU的程序,有两个特点:单线程思想,面向对象思想。
编写GPU的程序,有两个特点:千线程思想,面向数组思想

1.1 千线程思想

(这个千线程思想是我自己想出来的名词)CPU当然也有多线程程序,比如我的Macbook Pro是双核四线程,可以同时跑四个线程。但是CPU的多线程和GPU的多线程有两点本质区别:1)CPU的“多”,规模是十,然而GPU的“多”,规模是;2)CPU多线程之间的并行,是多个function之间的并行,然而GPU多线程的并行,是一个function内部的并行


图:本图和下文很多图片,来自这里

进一步解释第二点,假设一个function 内部是一个双层for loop i := 0 999,程序需要调用四次function 。那么CPU的程序会同时搞出四个线程,每个线程调用一次function,每次顺序执行for loop10002 次。而GPU的骚操作是,顺序调用四次function,每次搞出10002 个线程一下子解决这个双层for loop。当然了,你需要改写程序,改为function_gpu (GPU里面可以同时执行几千的线程,其他线程处于等待状态,不过你尽管搞出上百万个线程都没问题)。当然了,你可以CPU和GPU同时配合使用,CPU搞出四个线程,每个线程调用function_gpu ,这样子可以增大GPU的利用率。

这里申明很重要一点,不是所有的function 能(适合)改写为function_gpu 。不过很多机器学习相关的算法,很多计算密集行算法,是可以改写的。会把function 改写为function_gpu 是一种当下少数人掌握的技能。在本文chapter 3有三份入门代码。

1.2 面向数组思想

面向对象的编程思想是当下主流思想:一个对象有若干个属性,一个容器装很多个对象,如果想获取对象的属性,需要获取对象然后进行点操作。面向数组则完全不同。在做data science(DS) 和 machine learning(ML) 项目的时候,都是面向数组的思想。Numpy.ndarray和Pandas.DataFrame都是设计的非常棒的”超级数组”。

在DS和ML项目里面,数组之所以作为唯一钦定的数据结构,我认为是数组能够完美胜任DS和ML 项目里面组织和管理数据的工作。DS和ML项目完全不需要object-oriented的那一套封装和继承的思想,也不需要链表、栈、队列、哈希表、二叉树、B-树、图等其他数据结构。这背后的原因,我认为是DS和ML项目和那种企业级开发存在天然的区别。比如企业级开发需要处理复杂的业务逻辑,DS和ML项目就没有复杂的业务逻辑,只有对数组里的数据的反复“暴力计算”,这种对一个数组里的数据进行反复的暴力计算,正好是GPU说擅长的东西,有些SIMD(single instruction multiple data)的既视感。


图:截屏自这个YouTube视频。


2 图解GPU


图:CPU里面有很大的缓存(黄色),GPU里面缓存很少。CPU擅长复杂的程序控制(蓝色),但是ALU算术运算单元(绿色)比较少。GPU最大的特点就是ALU很多,擅长算数计算。


图:GPU加速的方法是,把程序里面计算密集型的CPU代码,改写为GPU代码。让CPU做CPU擅长的事情,让GPU做GPU擅长的事情,优势互补。


图:GPU是如何和内存和CPU相配合的,分为4个步骤。其中步骤1和4是很消耗时间的,实际编程的时候,需要考虑如何减少1和4。


图:CUDA是这样子组织上千个线程的,若干线程汇聚为一个block,若干block汇聚为一个grid。这就是CUDA的two-level thread hierarchy。深刻理解这个two-level对于编写CUDA程序十分重要。


图:GPU的内存模型。GPU里面的内存分为三种:per thread local memory, per block shared memory,和global memory。在实际编程的时候,需要考虑多用shared memory,少用global memory,因为shared比global的访问和存取速度快很多。


3 Talk is cheap, show me the code

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章