Python实现GPU加速的基本操作
作者:DECHIN 发布时间:2021-07-30 05:10:34
CUDA的线程与块
GPU从计算逻辑来讲,可以认为是一个高并行度的计算阵列,我们可以想象成一个二维的像围棋棋盘一样的网格,每一个格子都可以执行一个单独的任务,并且所有的格子可以同时执行计算任务,这就是GPU加速的来源。那么刚才所提到的棋盘,每一列都认为是一个线程,并有自己的线程编号;每一行都是一个块,有自己的块编号。我们可以通过一些简单的程序来理解这其中的逻辑:
用GPU打印线程编号
# numba_cuda_test.py
from numba import cuda
@cuda.jit
def gpu():
print ('threadIdx:', cuda.threadIdx.x)
if __name__ == '__main__':
gpu[2,4]()
threadIdx: 0
threadIdx: 1
threadIdx: 2
threadIdx: 3
threadIdx: 0
threadIdx: 1
threadIdx: 2
threadIdx: 3
用GPU打印块编号
# numba_cuda_test.py
from numba import cuda
@cuda.jit
def gpu():
print ('blockIdx:', cuda.blockIdx.x)
if __name__ == '__main__':
gpu[2,4]()
blockIdx: 0
blockIdx: 0
blockIdx: 0
blockIdx: 0
blockIdx: 1
blockIdx: 1
blockIdx: 1
blockIdx: 1
用GPU打印块的维度
# numba_cuda_test.py
from numba import cuda
@cuda.jit
def gpu():
print ('blockDim:', cuda.blockDim.x)
if __name__ == '__main__':
gpu[2,4]()
blockDim: 4
blockDim: 4
blockDim: 4
blockDim: 4
blockDim: 4
blockDim: 4
blockDim: 4
blockDim: 4
用GPU打印线程的维度
# numba_cuda_test.py
from numba import cuda
@cuda.jit
def gpu():
print ('gridDim:', cuda.gridDim.x)
if __name__ == '__main__':
gpu[2,4]()
gridDim: 2
gridDim: 2
gridDim: 2
gridDim: 2
gridDim: 2
gridDim: 2
gridDim: 2
gridDim: 2
总结
我们可以用如下的一张图来总结刚才提到的GPU网格的概念,在上面的测试案例中,我们在GPU上划分一块2*4大小的阵列用于我们自己的计算,每一行都是一个块,每一列都是一个线程,所有的网格是同时执行计算的内容的(如果没有逻辑上的依赖的话)。
GPU所支持的最大并行度
我们可以用几个简单的程序来测试一下GPU的并行度,因为每一个GPU上的网格都可以独立的执行一个任务,因此我们认为可以分配多少个网格,就有多大的并行度。本机的最大并行应该是在\(2^40\),因此假设我们给GPU分配\(2^50\)大小的网格,程序就会报错:
# numba_cuda_test.py
from numba import cuda
@cuda.jit
def gpu():
pass
if __name__ == '__main__':
gpu[2**50,1]()
print ('Running Success!')
运行结果如下:
Traceback (most recent call last):
File "numba_cuda_test.py", line 10, in <module>
gpu[2**50,1]()
File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/compiler.py", line 822, in __call__
self.stream, self.sharedmem)
File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/compiler.py", line 966, in call
kernel.launch(args, griddim, blockdim, stream, sharedmem)
File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/compiler.py", line 699, in launch
cooperative=self.cooperative)
File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/cudadrv/driver.py", line 2100, in launch_kernel
None)
File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/cudadrv/driver.py", line 300, in safe_cuda_api_call
self._check_error(fname, retcode)
File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/cudadrv/driver.py", line 335, in _check_error
raise CudaAPIError(retcode, msg)
numba.cuda.cudadrv.driver.CudaAPIError: [1] Call to cuLaunchKernel results in CUDA_ERROR_INVALID_VALUE
而如果我们分配一个额定大小之内的网格,程序就可以正常的运行:
# numba_cuda_test.py
from numba import cuda
@cuda.jit
def gpu():
pass
if __name__ == '__main__':
gpu[2**30,1]()
print ('Running Success!')
这里加了一个打印输出:
Running Success!
需要注意的是,两个维度上的可分配大小是不一致的,比如本机的上限是分配230*210大小的空间用于计算:
# numba_cuda_test.py
from numba import cuda
@cuda.jit
def gpu():
pass
if __name__ == '__main__':
gpu[2**30,2**10]()
print ('Running Success!')
同样的,只要在允许的范围内都是可以执行成功的:
Running Success!
如果在本机上有多块GPU的话,还可以通过select_device
的指令来选择执行指令的GPU编号:
# numba_cuda_test.py
from numba import cuda
cuda.select_device(1)
import time
@cuda.jit
def gpu():
pass
if __name__ == '__main__':
gpu[2**30,2**10]()
print ('Running Success!')
如果两块GPU的可分配空间一致的话,就可以运行成功:
Running Success!
GPU的加速效果
前面我们经常提到一个词叫GPU加速,GPU之所以能够实现加速的效果,正源自于GPU本身的高度并行性。这里我们直接用一个数组求和的案例来说明GPU的加速效果,这个案例需要得到的结果是\(b_j=a_j+b_j\),将求和后的值赋值在其中的一个输入数组之上,以节省一些内存空间。当然,如果这个数组还有其他的用途的话,是不能这样操作的。具体代码如下:
# gpu_add.py
from numba import cuda
cuda.select_device(1)
import numpy as np
import time
@cuda.jit
def gpu(a,b,DATA_LENGHTH):
idx = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x
if idx < DATA_LENGHTH:
b[idx] += a[idx]
if __name__ == '__main__':
np.random.seed(1)
DATA_EXP_LENGTH = 20
DATA_DIMENSION = 2**DATA_EXP_LENGTH
np_time = 0.0
nb_time = 0.0
for i in range(100):
a = np.random.randn(DATA_DIMENSION).astype(np.float32)
b = np.random.randn(DATA_DIMENSION).astype(np.float32)
a_cuda = cuda.to_device(a)
b_cuda = cuda.to_device(b)
time0 = time.time()
gpu[DATA_DIMENSION,4](a_cuda,b_cuda,DATA_DIMENSION)
time1 = time.time()
c = b_cuda.copy_to_host()
time2 = time.time()
d = np.add(a,b)
time3 = time.time()
if i == 0:
print ('The error between numba and numpy is: ', sum(c-d))
continue
np_time += time3 - time2
nb_time += time1 - time0
print ('The time cost of numba is: {}s'.format(nb_time))
print ('The time cost of numpy is: {}s'.format(np_time))
需要注意的是,基于Numba实现的Python的GPU加速程序,采用的jit即时编译的模式,也就是说,在运行调用到相关函数时,才会对其进行编译优化。换句话说,第一次执行这一条指令的时候,事实上达不到加速的效果,因为这个运行的时间包含了较长的一段编译时间。但是从第二次运行调用开始,就不需要重新编译,这时候GPU加速的效果就体现出来了,运行结果如下:
$ python3 gpu_add.py The error between numba and numpy is: 0.0
The time cost of numba is: 0.018711328506469727s
The time cost of numpy is: 0.09502553939819336s
可以看到,即使是相比于Python中优化程度十分强大的的Numpy实现,我们自己写的GPU加速的程序也能够达到5倍的加速效果(在前面一篇博客中,针对于特殊计算场景,加速效果可达1000倍以上),而且可定制化程度非常之高。
总结概要
本文针对于Python中使用Numba的GPU加速程序的一些基本概念和实现的方法,比如GPU中的线程和模块的概念,以及给出了一个矢量加法的代码案例,进一步说明了GPU加速的效果。需要注意的是,由于Python中的Numba实现是一种即时编译的技术,因此第一次运算时的时间会明显较长,所以我们一般说GPU加速是指从第二步开始的运行时间。对于一些工业和学界常见的场景,比如分子动力学模拟中的系统演化,或者是深度学习与量子计算中的参数优化,都是相同维度参数多步运算的一个过程,非常适合使用即时编译的技术,配合以GPU高度并行化的加速效果,能够在实际工业和学术界的各种场景下发挥巨大的作用。
来源:https://www.cnblogs.com/dechinphy/p/nbc.html
猜你喜欢
- 一、使用 print() 函数在 Python 中,print() 函数支持格式化输出,与 C 语言的 printf 类似。1. 格式化输出
- 本文实例为大家分享了python使用itchat实现手机控制电脑的具体代码,供大家参考,具体内容如下1.准备材料首先电脑上需要安装了pyth
- 一、查询操作可以使用Dataframe的index属性和columns属性获取行、列索引。import pandas as pddata =
- 有时需要根据项目的实际需求向spider传递参数以控制spider的行为,比如说,根据用户提交的url来控制spider爬取的网站。在这种情
- 网络爬虫网络爬虫是指在互联网上自动爬取网站内容信息的程序,也被称作网络蜘蛛或网络机器人。大型的爬虫程序被广泛应用于搜索引擎、数据挖掘等领域,
- 学习Python Web和Django开发不能只学习Python。我们有时必需借助其它技术比如AJAX实现我们想要的功能。今天我们就要利用D
- 需求:启动程序后,让用户输入工资,然后打印商品列表允许用户根据商品编号购买商品用户选择商品后,检测余额是否够,够就直接扣款,不够就提醒可随时
- 尽管 xml.etree.ElementTree 库通常用来做解析工作,其实它也可以创建XML文档。 例如,考虑如下这个函数:from xm
- 对Python字符串,除了比较老旧的%,以及用来替换掉%的format,及在python 3.6中加入的f这三种格式化方法以外,还有可以使用
- 最近在做搜索设计时,发现了两个容易纠结的小问题,在这里谈谈自己的一些分析。问题一:提交的关键字是哪个?凡客的这个例子中,搜索建议“时尚斜拉链
- 这篇文章主要介绍了Python JSON编解码方式原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
- 文通过一个操作实例来说明SQL中主标识列IDENTITY的使用技巧。要求:在 sql server 2005中,建立数据表book,在表bo
- 可以查看mysql文件目录my.ini文件,可以找到类似于 datadir="D:/beeagle/Program Files/M
- 因工作需要研究了支付宝即时到帐接口,并成功应用到网站上,把过程拿出来分享。即时到帐只是支付宝众多商家服务中的一个,表示客户付款,客户用支付宝
- 核心代码function convert2utf8($string) { return iconv(&
- 在SQL Server 2005中,它的另外一个强大的新特点是数据库快照。数据库快照是一个数据库的只读副本,它是数据库所有数据的映射,由快照
- for循环在Python中有遍历所有序列的项目,如列表或一个字符串。语法:for循环语法如下:for iterating_var
- 我们想要知道数目的总和,只要通过+就能实现,这是我们在做题上经常用到的符号。但是在python中不能直接使用,我们需要借助一些代码或者函数帮
- 导读:这篇文章主要介绍如何利用opencv来对图像添加各类噪声,原图:1、高斯噪声高斯噪声就是给图片添加一个服从高斯分布的噪声,可以通过调节
- 一、利用Google API生成二维码Google提供了较为完善的二维码生成接口,调用API接口很简单,以下是调用代码:$urlToEnco