什么是openCL范围尺寸?

时间:2019-04-29 22:34:25

标签: foreach parallel-processing range opencl gpgpu

我认为计算加速器(GPU)是一堆SP,即“ S 治疗 P 处理器”,每个SP都包含一些固定数量的ALU核心,以SIMD方式运行。但是,与cpu线程不同的是,SP一起触发,但步幅较大。这也称为合并。

例如,我有数组A,B和C。我会知道并管理它们的内部结构(是1D还是5D数组)-这与GPU无关。我只是这样说-“拿这对只读存储器A和B。取一个那只写存储器C。执行N次某些指令序列。”

“最了解”其内部“ SP”(或“ CU”)计数和高速缓存的GPU可以采用这种方式并将任务分割为相同的块。

因此,硬币的正面是每个DRAM都是FLAT。因此,PC中的所有事物本质上都是一维的。我不明白什么是2D,3D范围以及它们的用途。我们不能在任何地方都使用1D吗?

另一方面,让我们假设已经完成了,因为openCL方法声称非常灵活,甚至迫使我们为其提供内部数组结构。现在我有42维数据!为什么不支持它,但仅支持3个尺寸?

那么什么是局部全局组,ndranges维,以及如何计算它们?

您能否提供一些示例,说明多维范围至关重要或至少有益于使用?您如何拆分为本地,缓存和全局大小?

这里是我不了解的参数列表,并且完全弄糟了:

CL_DEVICE_GLOBAL_MEM_CACHE_SIZE
CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE
CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE
CL_DEVICE_LOCAL_MEM_SIZE
CL_DEVICE_MAX_WORK_ITEM_SIZES
CL_DEVICE_IMAGE2D_MAX_HEIGHT
CL_DEVICE_IMAGE3D_MAX_HEIGHT
CL_DEVICE_MAX_SAMPLERS
CL_DEVICE_MAX_COMPUTE_UNITS

是否有一些通用的公式来说明普通程序员如何使用它们,只是为了确保将他的工作分配到足够高效的GPU上?

2 个答案:

答案 0 :(得分:2)

好的,我会尽力解释这一点,但是您在一篇文章中提出了很多问题,看来您缺乏OpenCL的基本抽象。

主机:由主机决定OpenCL的处理方式。那就是运行程序的处理器。

计算设备:这是您的代码将在其上运行的硬件。图形卡(GPU)是单个设备,多核CPU也是如此。如果您的计算机上有两个GPU,则可以在两个设备上同时运行程序。

计算单元:在设备内,所有内核(用于Nvidia的CUDA内核,用于AMD的Stream处理器)被分成共享公共本地内存的组。从概念上讲,每个计算单元都可以看作是小型SIMD处理器。一台设备到另一台设备的组大小不同,但通常为32或64。(对于我的GTX 970,我在13个计算单元中拥有1664个CUDA内核,因此为128个)。我认为没有直接的方法可以使用clGetDeviceInfo进行查询,但是您可以轻松地针对给定的图形卡进行查询。

处理元素:这就是我们在GPU中命名单个内核的方式。它们没有任何存储器,只有寄存器。请注意,在任何给定时间,同一计算单元的每个处理元素都会同步运行同一程序。如果您的代码中有一堆逻辑(if/else)语句,并且某些处理元素与其他处理元素采用不同的分支,则所有其他处理元素将等待不执行任何操作。

程序:甚至对我来说也差不多。这是您的程序已加载到内存中,需要进行编译/编译。一个程序可能包含多个分别调用的功能(内核)。

内核:简而言之,这就是您将在设备上运行的功能。内核实例将在处理元素上运行。内核有许多实例同时运行。在内核中,可以获得有关正在运行的处理元素的一些信息。这是通过与clEnqueueNDRangeKernel的参数紧密相关的一些基本功能完成的(请参见下文)。

内存: 在内存方面,每个计算设备(GPU)都有一个全局内存,您可以从主机写入该内存。然后,每个计算单元将具有由该计算单元的处理元件共享的有限数量的本地存储器(CL_DEVICE_LOCAL_MEM_SIZE)。您可以分配的缓冲区大小有很多限制,但是通常这不是问题。您可以查询不同的CL_DEVICE_x参数来获取这些数字。全局内存中有一个“常量”部分,但我不会讨论,因为它不会给讨论带来任何好处。

要在GPU上执行计算时,需要内核和GPU内存中的一些缓冲区。主机(CPU)应该将内存转移到全局内存中的缓冲区中。它还应设置内核所需的参数。然后,它应该告诉GPU使用clEnqueueNDRangeKernel调用内核。此功能有很多参数...

globalWorkSize :每个维度上内核运行以解决问题的次数。尺寸的数量是任意的,该标准表示计算设备需要支持至少3个尺寸,但是某些GPU可能支持更多尺寸。没关系,因为任何ND问题都可以分为多个一维问题。

localWorkSize :这是每个维度由计算单元执行的工作量。通常,您希望使用与计算单元中的处理元素数量相对应的值(通常为32或64,请参见上文)。请注意,localWorkSize必须均匀划分globalWorkSize。 0 == (globalWorkSize % localWorkSize)

让我们举一个例子。假设我有一个1024个数字的1D数组,我只想对该数组中的每个值求平方。 globalWorkSize为1024,因为我希望每个数字都可以独立处理,因此我会将localWorkSize设置为计算单元中处理单元的最高数量,该单元平均分配1024(GTX970使用128)。我的问题是1维,所以我将1写入该参数。

请记住,如果使用的数量小于(或大于)计算单元中处理元素的数量,则其他元素只会消耗时钟周期而无济于事。我可以说我希望localWorkSize为2,但是每个计算单元将浪费126/128个处理元素,而且效率不高。

通过设置globalWorkSize = 1024localWorkSize = 128,我只是告诉我的GPU在(1024/128 = 8)个计算单元上运行内核1024次。我将有1024个处理元素(CUDA内核),每个处理元素都对缓冲区的1个元素执行操作。

现在,处理元素如何知道必须在缓冲区中计算出什么值?这就是工作项功能发挥作用的地方。

其中有一些,但是在此示例中,我仅关心get_global_id(uint nDimensions)。它基于globalWorkSize返回给定维度的全局ID。在我们的例子中,我们的问题是1d,因此get_global_id(0)将返回一个介于[0,globalWorkSize]之间的索引。每个处理元素的索引都不同。

示例内核:

__kernel MakeSquared(__global double* values) {
    size_t idx = get_global_id(0);
    values[idx] = values[idx] * values[idx];
}

编辑:本地内存使用示例:

__kernel MakeSquared(__global double* values, __local double* lValues) {
    size_t idx = get_global_id(0);
    size_t localId = get_local_id(0);

    lValues[localId] = values[idx];

    // potentially some complex calculations
    lValues[localId] = lValues[localId] * lValues[localId];


    values[idx] = lValues[localId];
}

还有很多话要说,但我想我已经讲完了基础知识。

答案 1 :(得分:0)

  

但是与cpu线程不同,SP一起触发,但步幅较大。这也称为合并。

您在这里混合了两个不同的概念:内存访问(/合并)和程序执行。 CU中的每个PE都在锁步中执行相同的指令(至少在大多数GPU上有一些例外),但是步伐或合并取决于程序员。例如,我可以编写一个全局工作大小为1000的内核,但是所有1000个工作项总共只能访问10个字节的内存。或仅1个字节。或随机顺序为10 MB。内存访问/合并与程序执行范围(全局/本地工作大小)无关。 IOW的本地/全局范围指定了将启动内核实例的数量。但是每个实例访问内存的方式都与此无关。

  

DRAM是FLAT。因此,PC中的所有事物本质上都是一维的。我不明白什么是2D,3D范围以及它们的用途。我们不能在任何地方都使用1D吗?

同样,范围与内存无关。至于为什么会有2D / 3D范围:

假设您有一个800x600的2D图像,并且想要运行sobel过滤器。如果只有一维范围,则可以在全局尺寸为1D的每个像素上运行内核480000。但是sobel滤镜需要来自上一个和下一个图像行的像素。因此,您必须根据1D值重新计算当前像素的x和y-这需要除法和取模。两者都很慢,您需要为每个像素执行此操作。具有2D / 3D范围的意义在于“ get_global_id”和朋友是硬件加速的。通常,GPU中的某些硬件(调度程序,CU或PE)会在某些特殊寄存器中跟踪当前正在执行的工作项的x,y,z,而get_global_id转换为一条读取寄存器的指令。

  

42维数据!为什么不支持它,但仅支持3个尺寸?

因为GPU架构师没有看到在3个以上维度上加速get_global_id和好友的意义。