编写CUDA内核
编写CUDA内核
介绍
与用于CPU编程的传统顺序模型不同,CUDA具有执行模型。在CUDA中,编写的代码将同时由多个线程(通常成百上千个)执行。解决方案将通过定义网格,块和线程层次结构进行建模。
Numba的CUDA支持提供了用于声明和管理此线程层次结构的工具。这些功能与NVidia的CUDA C语言开放的功能非常相似。
Numba还开放了三种GPU内存:全局设备内存(连接到GPU本身的大型,相对较慢的片外内存),片上 共享内存和本地内存。对于除最简单算法以外的所有算法,务必仔细考虑如何使用和访问内存,以最大程度地减少带宽需求和争用,这一点很重要。
内核声明
一个核心功能是指从CPU代码(*)称为GPU功能。它具有两个基本特征:
- 内核无法显式返回值;所有结果数据都必须写入传递给函数的数组中(如果计算标量,则可能传递一个单元素数组);
- 内核在调用时显式声明其线程层次结构:即线程块数和每个块的线程数(请注意,虽然内核仅编译一次,但可以使用不同的块大小或网格大小多次调用)。
用Numba编写CUDA内核看起来非常像为CPU编写JIT函数:
@cuda.jit
def increment_by_one(an_array):
"""
Increment all array elements by one.
"""
# code elided here; read further for different implementations
(*)注意:较新的CUDA支持设备端内核启动;此功能称为动态并行性,但Numba当前不支持它)
内核调用
通常以以下方式启动内核:
threadsperblock = 32
blockspergrid = (an_array.size + (threadsperblock - 1)) // threadsperblock
increment_by_one[blockspergrid, threadsperblock](an_array)
注意到两个步骤:
- 通过指定多个块(或“每个网格的块”)和每个块的线程数来实例化内核。两者的乘积将给出启动的线程总数。内核实例化是通过采用已编译的内核函数(在此处increment_by_one)并用整数元组对其进行索引来完成的。
- 通过将输入数组(如果需要,以及任何单独的输出数组)传递给内核来运行内核。内核异步运行:启动将其在设备上的执行排队,然后立即返回。可以 cuda.synchronize()用来等待所有先前的内核启动完成执行。
注意
传递驻留在主机内存中的数组,将隐式地导致将副本复制回主机,这将是同步的。在这种情况下,直到将数据复制回内核启动才会返回,因此似乎是同步执行的。
选择块大小
在声明内核所需的线程数时,具有两级层次结构似乎很奇怪。块大小(即每个块的线程数)通常很关键:
- 在软件方面,块大小确定多少线程共享内存的给定区域。
- 在硬件方面,块的大小必须足够大以完全占用执行单元。建议可在 CUDA C编程指南中找到。
多维块和网格
为了帮助处理多维数组,CUDA允许指定多维块和网格。在上面的示例中,可以使blockspergridandthreadsperblock元组为一个,两个或三个整数。与等效大小的一维声明相比,这不会改变所生成代码的效率或行为,但可以帮助以更自然的方式编写算法。
Thread线程定位
运行内核时,内核函数的代码由每个线程执行一次。因此,它必须知道它在哪个线程中,以便知道它负责哪个数组元素(复杂算法可以定义更复杂的职责,但是基本原理是相同的)。
一种方法是让线程确定其在网格和块中的位置,然后手动计算相应的数组位置:
@cuda.jit
def increment_by_one(an_array):
# Thread id in a 1D block
tx = cuda.threadIdx.x
# Block id in a 1D grid
ty = cuda.blockIdx.x
# Block width, i.e. number of threads per block
bw = cuda.blockDim.x
# Compute flattened index inside the array
pos = tx + ty * bw
if pos < an_array.size: # Check array boundaries
an_array[pos] += 1
注意
除非确定块大小和网格大小是阵列大小的除数,否则必须如上所述检查边界。
threadIdx,blockIdx,blockDim和gridDim 是由CUDA后端为知道Thread线程层次结构的几何形状和当前线程的该几何形状内的位置,唯一目的提供特殊对象。
这些对象可以是1D,2D或3D,具体取决于调用内核的方式 。在每个维度访问该值,可使用x,y并z分别这些对象的属性。
numba.cuda.threadIdx
当前线程块中的线程索引。对于1D块,索引(由x属性赋予)是一个整数,范围从0(包括)到numba.cuda.blockDim排除(exclusive)。当使用多个维度时,每个维度都存在类似的规则。
numba.cuda.blockDim
实例化内核时声明的线程块的形状。对于给定内核中的所有线程,即使属于不同的块(即,每个块“已满”),该值也相同。
numba.cuda.blockIdx
线程网格中的块索引启动了内核。对于一维网格,索引(由x属性赋予)是一个整数,范围从0(含)到numba.cuda.gridDim不包含(exclusive)。当使用多个维度时,每个维度都存在类似的规则。
numba.cuda.gridDim
实例化内核时,声明的块网格形状,即此内核调用启动的块总数。
绝对位置
简单的算法将倾向于总是以与上例相同的方式使用线程索引。Numba提供了其它工具来自动执行此类计算:
numba.cuda.grid(ndim )
返回当前线程在整个块网格中的绝对位置。 ndim应该与实例化内核时声明的维数相对应。如果ndim为1,则返回一个整数。如果ndim为2或3,则返回给定整数的元组。
numba.cuda.gridsize(ndim )
返回整个块网格中Thread线程的绝对尺寸(或形状)。 ndim与grid()上述含义相同。
使用这些功能,递增示例可以变成:
@cuda.jit
def increment_by_one(an_array):
pos = cuda.grid(1)
if pos < an_array.size:
an_array[pos] += 1
二维数组和线程网格的相同示例为:
@cuda.jit
def increment_a_2D_array(an_array):
x, y = cuda.grid(2)
if x < an_array.shape[0] and y < an_array.shape[1]:
an_array[x, y] += 1
注意,实例化内核时,网格计算仍必须手动完成,例如:
threadsperblock = (16, 16)
blockspergrid_x = math.ceil(an_array.shape[0] / threadsperblock[0])
blockspergrid_y = math.ceil(an_array.shape[1] / threadsperblock[1])
blockspergrid = (blockspergrid_x, blockspergrid_y)
increment_a_2D_array[blockspergrid, threadsperblock](an_array)
进一步阅读
请参阅《CUDA C编程指南》,以详细了解CUDA编程。
编写CUDA内核的更多相关文章
- 编写HSA内核
编写HSA内核 介绍 HSA提供类似于OpenCL的执行模型.指令由一组硬件线程并行执行.在某种程度上,这类似于 单指令多数据(SIMD)模型,但具有这样的便利:细粒度调度对于程序员而言是隐藏的,而不 ...
- 在Ubuntu上为Android系统编写Linux内核驱动程序(老罗学习笔记1)
这里,我们不会为真实的硬件设备编写内核驱动程序.为了方便描述为Android系统编写内核驱动程序的过程,我们使用一个虚拟的硬件设备,这个设备只有一个4字节的寄存器,它可读可写.想起我们第一次学习程序语 ...
- 在Ubuntu上为Android系统编写Linux内核驱动程序
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6568411 在智能手机时代,每个品牌的手机都有 ...
- 使用FormatMessage函数编写一个内核错误码查看器
在编写驱动程序的时候,常用的一个结构是NTSTATUS,它来表示操作是否成功,但是对于失败的情况它的返回码过多,不可能记住所有的情况,应用层有一个GetLastError函数,根据这个函数的返回值可以 ...
- 如何在cuda内核函数中产生随机数(host端调用,device端产生)
最近,需要在kernel函数中调用浮点型的随机数.于是上网搜了下相关资料,一种方式是自己手动写一个随机数的__device__函数,然后在调用的时候调用这个函数.另一种,原来cuda在toolkit中 ...
- c++编写webui内核 .
http://blog.csdn.net/sx1989827/article/details/8068779 #pragma once #include <mshtmhst.h> #inc ...
- 用JAVA编写浏览器内核之实现javascript的document对象与内置方法
原创文章.转载请注明. 阅读本文之前,您须要对浏览器怎样载入javascript有一定了解. 当然,对java与javascript本身也须要了解. 本文首先介绍浏览器载入并执行javascript的 ...
- [翻译]Go与C#对比 第三篇:编译、运行时、类型系统、模块和其它的一切
Go vs C#, Part 3: Compiler, Runtime, Type System, Modules, and Everything Else | by Alex Yakunin | S ...
- 布客·ApacheCN 翻译/校对/笔记整理活动进度公告 2020.1
注意 请贡献者查看参与方式,然后直接在 ISSUE 中认领. 翻译/校对三个文档就可以申请当负责人,我们会把你拉进合伙人群.翻译/校对五个文档的贡献者,可以申请实习证明. 请私聊片刻(52981514 ...
随机推荐
- hdu 5063 不错的小想法题(逆向处理操作)
题意: 刚开始的时候给你一个序列,长度为n,分别为a[1]=1,a[2]=2,a[3]=3,a[4]=4...a[n]=n,然后有4种操作如下: Type1: O 1 call fun1( ...
- Windows Pe 第三章 PE头文件(上)
第三章 PE头文件 本章是全书重点,所以要好好理解,概念比较多,但是非常重要. PE头文件记录了PE文件中所有的数据的组织方式,它类似于一本书的目录,通过目录我们可以快速定位到某个具体的章节:通过P ...
- web php wrong nginx config
web php wrong nginx config 目录 web php wrong nginx config 题目描述 解题过程 信息收集 robots.txt hint.php Hack.php ...
- 【JavaScript】【KMP】Leetcode每日一题-实现strStr()
[JavaScript]Leetcode每日一题-实现strStr() [题目描述] 实现 strStr() 函数. 给你两个字符串 haystack 和 needle ,请你在 haystack 字 ...
- springboot开发浅谈 2021/05/11
学习了这么久,本人希望有时间能分享一下,这才写下这篇浅谈,谈谈软件,散散心情. 这是本人的博客园账号,欢迎关注,一起学习. 一开始学习springboot,看了好多网站,搜了好多课程.零零落落学了一些 ...
- Kafka源码分析系列-目录(收藏不迷路)
持续更新中,敬请关注! 目录 <Kafka源码分析>系列文章计划按"数据传递"的顺序写作,即:先分析生产者,其次分析Server端的数据处理,然后分析消费者,最后再补充 ...
- 一文弄懂pytorch搭建网络流程+多分类评价指标
讲在前面,本来想通过一个简单的多层感知机实验一下不同的优化方法的,结果写着写着就先研究起评价指标来了,之前也写过一篇:https://www.cnblogs.com/xiximayou/p/13700 ...
- 使用git rebase去掉无谓的融合
git pull 預設的行為是將遠端的 repo. 與本地的 repo. 合併,這也是 DVCS 的初衷,將兩個 branch 合併.但是,很多時候會發生以下這種情形: 這是因為,我們團隊的開發模式是 ...
- Unreal: Dynamic load map from Pak file
Unreal: Dynamic load map from Pak file 目标:在程序运行时加载自定义 Pak 文件,并打开指定关卡,显示其中的完整 map 内容 Unreal 的 Pak 文件内 ...
- 查阅日志文件:有时候报错信息只是给出了问题的表面现象,要想更深入的了解问题,必须查看相应的日志文件,而日志文件又分为系统日志文件(/var/log)和应用的日志文件,结合这两个日志文件,一般就能定位问题所在。
作为一名合格的 Linux 运维工程师,一定要有一套清晰.明确的解决故障思路,当问题出现时,才能迅速定位.解决问题,这里给出一个处理问题的一般思路: 重视报错提示信息:每个错误的出现,都是给出错误提示 ...