按索引

时间:2017-05-09 00:08:19

标签: ios macos gpu gpgpu metal

注意:我的问题是关于Apple的Metal API,但我认为这个概念足以转换为其他GPU框架。

我的目标:向1 x N矩阵b中的每一行添加M x N行向量A

我的内核,缩减到我遇到问题的部分:

kernel void vmadd(const device float* A [[ buffer(0) ]],
                  const device float* b [[ buffer(1) ]],
                  device float* C [[ buffer(2) ]],
                  constant ushort& aWidth [[ buffer(3) ]],
                  ushort2 gid [[ thread_position_in_grid ]]) {

    int idx = gid.y * aWidth + gid.x; // Compute absolute index in C
    C[idx] = A[idx] + b[gid.x];

}

假设:我的理解是gidC中单个元素的位置:gid.x是列,gid.y是排。如果不是这样,有人请指正。

现在,如果我用8 x 8零填充A

A = 0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0
    0 0 0 0 0 0 0 0

b喜欢这样:

b = 1 2 3 4 5 6 7 8

然后执行后,C应为8 x 8矩阵,其中每行为1 2 3 4 5 6 7 8

相反,我明白了:

C = 1 2 3 4 5 6 7 8
    1 2 3 4 5 6 7 8
    1 2 3 4 5 6 7 8
    0 0 0 0 0 0 0 0
    1 2 3 4 5 6 7 8
    1 2 3 4 5 6 7 8
    1 2 3 4 5 6 7 8
    0 0 0 0 0 0 0 0

我正在根据Apple的建议here调整线程组:

let w = computePipeline.threadExecutionWidth
let h = computePipeline.maxTotalThreadsPerThreadgroup / w
let threadsPerThreadgroup = MTLSizeMake(w, h, 1)
let threadgroupsPerGrid = MTLSize(width: (cWidth + w - 1) / w,
                                  height: (cHeight + h - 1) / h,
                                  depth: 1)

在我的机器上,每个组生成(64, 16, 1)个线程,每个网格生成(1, 1, 1)个线程组。

但是,如果我手动将threadsPerThreadgroup设置为(1, 1, 1)而将threadgroupsPerGrid设置为(8, 8, 1),我会在C中获得正确的结果。

问题:

我很确定我的问题与线程组大小调整和缓冲区的管理方式有关,但我对GPU编程还不熟悉,所以我完全不了解它。

为什么减小线程组的大小会产生正确的结果?更一般地说,为什么线程组大小调整对这个计算有任何影响?

在我看来,如果gid始终与C中的索引相对应,并且我要求b[gid.x],那么该值应该可用。我知道数据通常被分解以适应线程组缓存 - 如果是这种情况,它遵循什么规则以及如何解释它?

1 个答案:

答案 0 :(得分:2)

  

我的理解是gidC中单个元素的位置:gid.x是列,gid.y是行。如果不是这样,有人请指正。

这不是真的。 gid网格中的位置。

因为网格实际上是64x16,所以将调用计算函数用于超出8x8矩阵(AC)和8元素向量({{}的位置。 {1}})。发生这种情况时,b的读取可能会访问错误的行,甚至可能会超出A的末尾。同样,A的读取将超出其结束。

例如,考虑b何时为(8,0)。 gid将为8.您将阅读idx,实际上是(0,1)。你会看到A[8],它已经过了结束。这在技术上是未定义的,但对于相对较短长度的缓冲区,实际上很可能为0。你会写到b[8],也是(0,1)。这种情况大致同时发生,因为函数调用假定写入(0,1),并且存在争用的争议。

你的函数应该在开头附近测试C[8]是否超出范围,如果是,则提前返回:

gid

(这假定if (any(gid > aWidth)) return; A将始终为方形,因此可以针对单个值检查宽度和高度。)

您可以尝试调整CthreadsPerThreadgroup的计算,以使网格与矩阵的大小完全相同,但对于所有情况而言,这可能会非常繁琐。也就是说,你当然可以让threadgroupsPerGrid过大:

threadsPerThreadgroup

但是你仍然需要检查计算功能,因为总网格仍然太大。例如,假设let w = min(computePipeline.threadExecutionWidth, cWidth) let h = min(computePipeline.maxTotalThreadsPerThreadgroup / w, cHeight) 至少为8而computePipeline.threadExecutionWidth为60.那么,computePipeline.maxTotalThreadsPerThreadgroup将为8,但w将为7.然后,h将是(1,2,1),总网格尺寸为8x14x1,再次大于矩阵。