PyCUDA 可以通过 Python 访问 NVIDIA 的 CUDA 并行计算 API。

具体介绍和安装可以参考 PyCUDA 官网文档和 pycuda PyPI

本文涵盖的内容有:

  1. 通过 PyCUDA 查询 GPU 信息。
  2. NumPy array 和 gpuarray 之间的相互转换。
  3. 使用 gpuarray 进行基本的运算。
  4. 使用 ElementwiseKernel 进行按元素的运算。
  5. 使用 InclusiveScanKernel 和 ReductionKernel 的 reduce 操作。

本文示例在 GPU 环境下,使用 Jupyter Notebook 导入了以下包:

 import sys
from time import time
from functools import reduce import numpy as np
import pandas as pd
import matplotlib
from matplotlib import pyplot as plt
from IPython.core.interactiveshell import InteractiveShell import pycuda
import pycuda.autoinit
import pycuda.driver as drv
from pycuda import gpuarray
from pycuda.elementwise import ElementwiseKernel
from pycuda.scan import InclusiveScanKernel
from pycuda.reduction import ReductionKernel InteractiveShell.ast_node_interactivity = "all"
print(f'The version of PyCUDA: {pycuda.VERSION}')
print(f'The version of Python: {sys.version}')

输出:

The version of PyCUDA: (2019, 1, 2)
The version of Python: 3.6.6 |Anaconda, Inc.| (default, Oct 9 2018, 12:34:16)
[GCC 7.3.0]

查询 GPU 信息

GPU 查询是一个非常基本的操作,比较常用的重要信息有 GPU 设备名、GPU 显存、核心数量等。

定义函数:

 def query_device():
drv.init()
print('CUDA device query (PyCUDA version) \n')
print(f'Detected {drv.Device.count()} CUDA Capable device(s) \n')
for i in range(drv.Device.count()): gpu_device = drv.Device(i)
print(f'Device {i}: {gpu_device.name()}')
compute_capability = float( '%d.%d' % gpu_device.compute_capability() )
print(f'\t Compute Capability: {compute_capability}')
print(f'\t Total Memory: {gpu_device.total_memory()//(1024**2)} megabytes') # The following will give us all remaining device attributes as seen
# in the original deviceQuery.
# We set up a dictionary as such so that we can easily index
# the values using a string descriptor. device_attributes_tuples = gpu_device.get_attributes().items()
device_attributes = {} for k, v in device_attributes_tuples:
device_attributes[str(k)] = v num_mp = device_attributes['MULTIPROCESSOR_COUNT'] # Cores per multiprocessor is not reported by the GPU!
# We must use a lookup table based on compute capability.
# See the following:
# http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#compute-capabilities cuda_cores_per_mp = { 5.0 : 128, 5.1 : 128, 5.2 : 128, 6.0 : 64, 6.1 : 128, 6.2 : 128}[compute_capability] print(f'\t ({num_mp}) Multiprocessors, ({cuda_cores_per_mp}) CUDA Cores / Multiprocessor: {num_mp*cuda_cores_per_mp} CUDA Cores') device_attributes.pop('MULTIPROCESSOR_COUNT') for k in device_attributes.keys():
print(f'\t {k}: {device_attributes[k]}')

执行 GPU 查询操作:

CUDA device query (PyCUDA version) 

Detected 1 CUDA Capable device(s) 

Device 0: Tesla P100-PCIE-16GB
Compute Capability: 6.0
Total Memory: 16280 megabytes
(56) Multiprocessors, (64) CUDA Cores / Multiprocessor: 3584 CUDA Cores
ASYNC_ENGINE_COUNT: 2
CAN_MAP_HOST_MEMORY: 1
CLOCK_RATE: 1328500
COMPUTE_CAPABILITY_MAJOR: 6
COMPUTE_CAPABILITY_MINOR: 0
COMPUTE_MODE: DEFAULT
CONCURRENT_KERNELS: 1
ECC_ENABLED: 1
GLOBAL_L1_CACHE_SUPPORTED: 1
GLOBAL_MEMORY_BUS_WIDTH: 4096
GPU_OVERLAP: 1
INTEGRATED: 0
KERNEL_EXEC_TIMEOUT: 0
L2_CACHE_SIZE: 4194304
LOCAL_L1_CACHE_SUPPORTED: 1
MANAGED_MEMORY: 1
MAXIMUM_SURFACE1D_LAYERED_LAYERS: 2048
MAXIMUM_SURFACE1D_LAYERED_WIDTH: 32768
MAXIMUM_SURFACE1D_WIDTH: 32768
MAXIMUM_SURFACE2D_HEIGHT: 65536
MAXIMUM_SURFACE2D_LAYERED_HEIGHT: 32768
MAXIMUM_SURFACE2D_LAYERED_LAYERS: 2048
MAXIMUM_SURFACE2D_LAYERED_WIDTH: 32768
MAXIMUM_SURFACE2D_WIDTH: 131072
MAXIMUM_SURFACE3D_DEPTH: 16384
MAXIMUM_SURFACE3D_HEIGHT: 16384
MAXIMUM_SURFACE3D_WIDTH: 16384
MAXIMUM_SURFACECUBEMAP_LAYERED_LAYERS: 2046
MAXIMUM_SURFACECUBEMAP_LAYERED_WIDTH: 32768
MAXIMUM_SURFACECUBEMAP_WIDTH: 32768
MAXIMUM_TEXTURE1D_LAYERED_LAYERS: 2048
MAXIMUM_TEXTURE1D_LAYERED_WIDTH: 32768
MAXIMUM_TEXTURE1D_LINEAR_WIDTH: 134217728
MAXIMUM_TEXTURE1D_MIPMAPPED_WIDTH: 16384
MAXIMUM_TEXTURE1D_WIDTH: 131072
MAXIMUM_TEXTURE2D_ARRAY_HEIGHT: 32768
MAXIMUM_TEXTURE2D_ARRAY_NUMSLICES: 2048
MAXIMUM_TEXTURE2D_ARRAY_WIDTH: 32768
MAXIMUM_TEXTURE2D_GATHER_HEIGHT: 32768
MAXIMUM_TEXTURE2D_GATHER_WIDTH: 32768
MAXIMUM_TEXTURE2D_HEIGHT: 65536
MAXIMUM_TEXTURE2D_LINEAR_HEIGHT: 65000
MAXIMUM_TEXTURE2D_LINEAR_PITCH: 2097120
MAXIMUM_TEXTURE2D_LINEAR_WIDTH: 131072
MAXIMUM_TEXTURE2D_MIPMAPPED_HEIGHT: 32768
MAXIMUM_TEXTURE2D_MIPMAPPED_WIDTH: 32768
MAXIMUM_TEXTURE2D_WIDTH: 131072
MAXIMUM_TEXTURE3D_DEPTH: 16384
MAXIMUM_TEXTURE3D_DEPTH_ALTERNATE: 32768
MAXIMUM_TEXTURE3D_HEIGHT: 16384
MAXIMUM_TEXTURE3D_HEIGHT_ALTERNATE: 8192
MAXIMUM_TEXTURE3D_WIDTH: 16384
MAXIMUM_TEXTURE3D_WIDTH_ALTERNATE: 8192
MAXIMUM_TEXTURECUBEMAP_LAYERED_LAYERS: 2046
MAXIMUM_TEXTURECUBEMAP_LAYERED_WIDTH: 32768
MAXIMUM_TEXTURECUBEMAP_WIDTH: 32768
MAX_BLOCK_DIM_X: 1024
MAX_BLOCK_DIM_Y: 1024
MAX_BLOCK_DIM_Z: 64
MAX_GRID_DIM_X: 2147483647
MAX_GRID_DIM_Y: 65535
MAX_GRID_DIM_Z: 65535
MAX_PITCH: 2147483647
MAX_REGISTERS_PER_BLOCK: 65536
MAX_REGISTERS_PER_MULTIPROCESSOR: 65536
MAX_SHARED_MEMORY_PER_BLOCK: 49152
MAX_SHARED_MEMORY_PER_MULTIPROCESSOR: 65536
MAX_THREADS_PER_BLOCK: 1024
MAX_THREADS_PER_MULTIPROCESSOR: 2048
MEMORY_CLOCK_RATE: 715000
MULTI_GPU_BOARD: 0
MULTI_GPU_BOARD_GROUP_ID: 0
PCI_BUS_ID: 0
PCI_DEVICE_ID: 4
PCI_DOMAIN_ID: 0
STREAM_PRIORITIES_SUPPORTED: 1
SURFACE_ALIGNMENT: 512
TCC_DRIVER: 0
TEXTURE_ALIGNMENT: 512
TEXTURE_PITCH_ALIGNMENT: 32
TOTAL_CONSTANT_MEMORY: 65536
UNIFIED_ADDRESSING: 1
WARP_SIZE: 32

在这里,我们发现了有一个 GPU 设备 Tesla P100-PCIE-16GB,其显存为 16G核心数目为 3584 个

NumPy array 和 gpuarray 之间的相互转换

GPU 有自己的显存,这区别于主机上的内存,这又称为设备内存(device memory)

NumPy array 运行在 CPU 环境(主机端),而 gpuarray 运行在 GPU 环境(设备端),两者常常需要相互转换,即 CPU 数据和 GPU 数据之间的传输转换。

 host_data = np.array([1, 2, 3, 4, 5], dtype=np.float32)
device_data = gpuarray.to_gpu(host_data)
device_data_x2 = 2 * device_data
host_data_x2 = device_data_x2.get()
print(host_data_x2)

其输出:

[ 2.  4.  6.  8. 10.]

进行转换的时候应该尽可能通过 dtype 指定类型,以避免不必要的性能损失。

gpuarray 的基本运算

按元素运算是天生的可并行计算的操作类型,在进行这种运算时 gpuarray 会自动利用多核进行并行计算。

 x_host = np.array([1, 2, 3], dtype=np.float32)
y_host = np.array([1, 1, 1], dtype=np.float32)
z_host = np.array([2, 2, 2], dtype=np.float32)
x_device = gpuarray.to_gpu(x_host)
y_device = gpuarray.to_gpu(y_host)
z_device = gpuarray.to_gpu(z_host) x_host + y_host
(x_device + y_device).get() x_host ** z_host
(x_device ** z_device).get() x_host / x_host
(x_device / x_device).get() z_host - x_host
(z_device - x_device).get() z_host / 2
(z_device / 2).get() x_host - 1
(x_device - 1).get()

输出:

array([2., 3., 4.], dtype=float32)
array([2., 3., 4.], dtype=float32)
array([1., 4., 9.], dtype=float32)
array([1., 4., 9.], dtype=float32)
array([1., 1., 1.], dtype=float32)
array([1., 1., 1.], dtype=float32)
array([ 1., 0., -1.], dtype=float32)
array([ 1., 0., -1.], dtype=float32)
array([1., 1., 1.], dtype=float32)
array([1., 1., 1.], dtype=float32)
array([0., 1., 2.], dtype=float32)
array([0., 1., 2.], dtype=float32)

性能比较

 def simple_speed_test():
host_data = np.float32(np.random.random(50000000)) t1 = time()
host_data_2x = host_data * np.float32(2)
t2 = time() print(f'total time to compute on CPU: {t2 - t1}') device_data = gpuarray.to_gpu(host_data) t1 = time()
device_data_2x = device_data * np.float32(2)
t2 = time() from_device = device_data_2x.get() print(f'total time to compute on GPU: {t2 - t1}')
print(f'Is the host computation the same as the GPU computation? : {np.allclose(from_device, host_data_2x)}') simple_speed_test()

如果是第一次执行会输出类似:

total time to compute on CPU: 0.14141535758972168
total time to compute on GPU: 2.010883092880249
Is the host computation the same as the GPU computation? : True

而后面再继续执行几次,会有类似的输出:

total time to compute on CPU: 0.1373155117034912
total time to compute on GPU: 0.0006959438323974609
Is the host computation the same as the GPU computation? : True

这是因为在 PyCUDA 中,通常会在程序第一次运行过程中,nvcc 编译器会对 GPU 代码进行编译,然后由 PyCUDA 进行调用。这个编译时间就是额外的性能损耗

ElementwiseKernel:按元素运算

我们先看一下 Python 的内置函数 map

第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的迭代器(Python2 中 map 输出的是列表),我们用 list() 把迭代器转换为列表观察结果。

list(map(lambda x: x + 10, [1, 2, 3, 4, 5]))

输出:

[11, 12, 13, 14, 15]

ElementWiseKernel 非常类似于 map 函数。

ElementwiseKernel 函数可以自定义按元素运算内核。使用时需要嵌入 CUDA C 的代码。

内核(kernel)在这里可以简单理解为 CUDA 直接运行在 GPU 的函数

看代码:

 gpu_2x_ker = ElementwiseKernel(
"float *in, float *out",
"out[i] = 2 * in[i];",
"gpu_2x_ker"
) def elementwise_kernel_example():
host_data = np.float32(np.random.random(50000000))
t1 = time()
host_data_2x = host_data * np.float32(2)
t2 = time()
print(f'total time to compute on CPU: {t2 - t1}') device_data = gpuarray.to_gpu(host_data)
# allocate memory for output
device_data_2x = gpuarray.empty_like(device_data) t1 = time()
gpu_2x_ker(device_data, device_data_2x)
t2 = time()
from_device = device_data_2x.get()
print(f'total time to compute on GPU: {t2 - t1}')
print(f'Is the host computation the same as the GPU computation? : {np.allclose(from_device, host_data_2x)}') elementwise_kernel_example()
elementwise_kernel_example()
elementwise_kernel_example()
elementwise_kernel_example()
elementwise_kernel_example()

输出:

total time to compute on CPU: 0.13545799255371094
total time to compute on GPU: 0.4059629440307617
Is the host computation the same as the GPU computation? : True
total time to compute on CPU: 0.13948774337768555
total time to compute on GPU: 0.0001266002655029297
Is the host computation the same as the GPU computation? : True
total time to compute on CPU: 0.1357274055480957
total time to compute on GPU: 0.0001552104949951172
Is the host computation the same as the GPU computation? : True
total time to compute on CPU: 0.13451647758483887
total time to compute on GPU: 0.0001761913299560547
Is the host computation the same as the GPU computation? : True
total time to compute on CPU: 0.1362597942352295
total time to compute on GPU: 0.00011849403381347656
Is the host computation the same as the GPU computation? : True

同样我们发现在第一次运行时,出现了 nvcc 编译产生的性能损耗。

ElementwiseKernel 的参数:

class pycuda.elementwise.ElementwiseKernel(argumentsoperationname="kernel"keep=Falseoptions=[]preamble="")

  • arguments:该内核定义的传参。
  • operation:该内核定义的内嵌 CUDA C 代码。
  • name:定义的内核名称。

gpuarray.empty_like 用于分配与 device_data 相同形状和类型的内存空间。

InclusiveScanKernel 和 ReductionKernel 的 reduce 操作

我们先看一下 Python 标准包 functools 中的 reduce 函数

reduce(lambda x, y : x + y, [1, 2, 3, 4])

输出:

10

与 map 函数不同,reduce 执行迭代的二元运算,只输出一个单值

我们将使用 InclusiveScanReductionKernel 来实现类似于 reduce 的操作。

InclusiveScanKernel

InclusiveScanKernel 类似于 reduce,因为它并非输出单值,输出与输入形状相同。

计算求和的操作,输出是一个累加的序列:

 seq = np.array([1, 2, 3, 4], dtype=np.int32)
seq_gpu = gpuarray.to_gpu(seq)
sum_gpu = InclusiveScanKernel(np.int32, "a+b")
print(sum_gpu(seq_gpu).get())
print(np.cumsum(seq))

输出:

[ 1  3  6 10]
[ 1 3 6 10]

查找最大值(最大值向后冒泡):

 seq = np.array([1,100,-3,-10000, 4, 10000, 66, 14, 21], dtype=np.int32)
seq_gpu = gpuarray.to_gpu(seq)
max_gpu = InclusiveScanKernel(np.int32, "a > b ? a : b")
seq_max_bubble = max_gpu(seq_gpu)
print(seq_max_bubble)
print(seq_max_bubble.get()[-1])
print(np.max(seq))

输出:

[    1   100   100   100   100 10000 10000 10000 10000]
10000
10000

对于 a > b ? a : b ,我们可以想象是做从前往后做一个遍历(实际是并行的),而对于每个当前元素 cur,都和前一个元素做比较,把最大值赋值给 cur。

这样,最大值就好像“冒泡”一样往后移动,最终取最后一个元素即可。

ReductionKernel

实际上,ReductionKernel 就像是执行 ElementWiseKernel 后再执行一个并行扫描内核。

一个计算两向量内积的例子:

 a_host = np.array([1, 2, 3], dtype=np.float32)
b_host = np.array([4, 5, 6], dtype=np.float32)
print(a_host.dot(b_host)) dot_prod = ReductionKernel(np.float32, neutral="", reduce_expr="a+b",
map_expr="x[i]*y[i]", arguments="float *x, float *y")
a_device = gpuarray.to_gpu(a_host)
b_device = gpuarray.to_gpu(b_host)
print(dot_prod(a_device, b_device).get())
32.0
32.0

首先对两向量的每个元素进行 map_expr 的计算,其结果再进行 reduce_expr 的计算(neutral 表示初始值),最终得到两向量的内积。

好了,到此为止,就是初识 PyCUDA 的一些操作。

原文作者:雨先生
原文链接:https://www.cnblogs.com/noluye/p/11465389.html  
许可协议:知识共享署名-非商业性使用 4.0 国际许可协议

参考

【GPU加速系列】PyCUDA(一):上手简单操作的更多相关文章

  1. XML系列之--对电文格式XML的简单操作(三)

    前两章介绍了关于Linq创建.解析SOAP格式的XML,在实际运用中,可能会对xml进行一些其它的操作,比如基础的增删该查,而操作对象首先需要获取对象,针对于DOM操作来说,Linq确实方便了不少,如 ...

  2. Selenium系列(三) - 针对元素常见的简单操作

    如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...

  3. ZooKeeper系列3:ZooKeeper命令、命令行工具及简单操作

    问题导读1.ZooKeeper包含哪些常用命令?2.通过什么命令可以列出服务器 watch 的详细信息?3.ZooKeeper包含哪些操作?4.ZooKeeper如何创建zookeeper? 常用命令 ...

  4. 56 Marvin: 一个支持GPU加速、且不依赖其他库(除cuda和cudnn)的轻量化多维深度学习(deep learning)框架介绍

    0 引言 Marvin是普林斯顿视觉实验室(PrincetonVision)于2015年提出的轻量化GPU加速的多维深度学习网络框架.该框架采用纯c/c++编写,除了cuda和cudnn以外,不依赖其 ...

  5. Python笔记_第四篇_高阶编程_进程、线程、协程_5.GPU加速

    Numba:高性能计算的高生产率 在这篇文章中,笔者将向你介绍一个来自Anaconda的Python编译器Numba,它可以在CUDA-capable GPU或多核cpu上编译Python代码.Pyt ...

  6. Python实现GPU加速的基本操作

    技术背景 之前写过一篇讲述如何使用pycuda来在Python上写CUDA程序的博客.这个方案的特点在于完全遵循了CUDA程序的写法,只是支持了一些常用函数的接口,如果你需要自己写CUDA算子,那么就 ...

  7. mxnet:结合R与GPU加速深度学习

    转载于统计之都,http://cos.name/tag/dmlc/,作者陈天奇 ------------------------------------------------------------ ...

  8. windows 10 64bit下安装Tensorflow+Keras+VS2015+CUDA8.0 GPU加速

    原文地址:http://www.jianshu.com/p/c245d46d43f0 写在前面的话 2016年11月29日,Google Brain 工程师团队宣布在 TensorFlow 0.12 ...

  9. Javascript如何实现GPU加速?

    一.什么是Javascript实现GPU加速? CPU与GPU设计目标不同,导致它们之间内部结构差异很大.CPU需要应对通用场景,内部结构非常复杂.而GPU往往面向数据类型统一,且相互无依赖的计算.所 ...

随机推荐

  1. 【CFGym102059G】Fascination Street(思维DP)

    点此看题面 大致题意: 有\(n\)个路灯,每个路灯有一定的建造费用,且建成后可照亮自身及周围距离为\(1\)的两个格子.你可以交换\(k\)次两个路灯的建造费用,求照亮所有格子的最小费用. 题意转换 ...

  2. Jenkins的简介及安装

    Jenkins介绍 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. 1. jenkins就是 ...

  3. Web协议详解与抓包实战:HTTP1协议-请求与响应的上下文(7)

    一.请求的上下文: User-Agent 指明客户端的类型信息,服务器可以据此对资源的表述做抉择 二.请求的上下文: Referer 浏览器对来自某一页面的请求自动添加的头部 截图2 这对于我们的防盗 ...

  4. Harbor 忘记密码

    Harbor密码重置 01,登入到harbor容器里面的数据库上 docker exec -it harbor-db /bin/bash 02,登入数据库 psql -h postgresql -d ...

  5. 实战django(二)--登录实现记住我

    上节初步实现了登录和注册模块,这节我们进一步实现“记住我”功能. 大体功能分为以下模块: 1.在登录时如果勾选记住我,那么就将用户username存进cookie中,跳转到index页面: 2.此时, ...

  6. setInterval()调用其他函数时候报错

    (function(){ function shortcut() { // 配件优化 window.topValue = 0// 上次滚动条到顶部的距离 window.interval = null; ...

  7. 调试MATLAB代码

    1.在子函数设置的断点,在运行时,不起作用: 因为在主函数开始时,使用了clear all,在运行时,会把断点给删除.

  8. 使用velero进行kubernetes灾备

    使用velero可以对集群进行备份和恢复,降低集群DR造成的影响.velero的基本原理就是将集群的数据备份到对象存储中,在恢复的时候将数据从对象存储中拉取下来.可以从官方文档查看可接收的对象存储,本 ...

  9. SQL Inserted 触发器游标结合实例

    SqlServer的Inserted可能是一个集合而不是一条数据,如果有如果需要对插入数据进行处理,有时需要用游标逐条处理 FROM inserted) --插入和更新 declare cur cur ...

  10. Spring Cloud灰度发布之Nepxion Discovery

    <蓝绿部署.红黑部署.AB测试.灰度发布.金丝雀发布.滚动发布的概念与区别> 最近公司项目在做架构升级,升级为 Spring Cloud,我们希望能够做到服务的灰度发布,根据访问量逐渐切换 ...