注意:我的问题与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];
}
假设:我的理解是
gid
是C
中单个元素的位置: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]
,则该值应该可用。我了解数据通常会被分解以适合线程组缓存-如果是这种情况,它将遵循哪些规则,我该如何处理? 最佳答案
我的理解是gid
是C
中单个元素的位置:gid.x
是列,gid.y
是行。如果不是这样,请有人纠正我。
这不是真的。 gid
是网格中的位置。
因为网格恰好是64x16,所以将调用位于8x8矩阵(A
和C
)和8元素向量(b
)之外的位置的计算功能。发生这种情况时,读取A
可能会访问错误的行,甚至可能访问A
的末尾。同样,对b
的读取将超出其末尾。
例如,考虑gid
是(8,0)的时间。 idx
将为8。您将阅读A[8]
,它实际上位于(0,1)。您将阅读b[8]
,这已经结束了。这在技术上是不确定的,但实际上对于一个相对较短的缓冲区,它很可能为0。您将写入也是(0,1)的C[8]
。这与应该在(0,1)编写的函数调用大致同时发生,并且存在一个竞争。
您的函数应该在开始时测试gid
是否超出范围,如果是,则尽早返回:
if (any(gid > aWidth))
return;
(这假定
A
和C
始终为正方形,因此可以根据单个值检查宽度和高度。)您可以尝试调整
threadsPerThreadgroup
和threadgroupsPerGrid
的计算,以使网格正好与您的矩阵大小相同,但是要在所有情况下正确进行操作都可能很麻烦。也就是说,您当然可以避免threadsPerThreadgroup
太大:let w = min(computePipeline.threadExecutionWidth, cWidth)
let h = min(computePipeline.maxTotalThreadsPerThreadgroup / w, cHeight)
但是您仍然需要在计算功能中进行检查,因为总网格仍然可能太大。例如,假设
computePipeline.threadExecutionWidth
至少为8,而computePipeline.maxTotalThreadsPerThreadgroup
为60。那么,w
将为8,但h
将为7。然后,threadgroupsPerGrid
将为(1、2、1),总网格大小将为8x14x1,再次比矩阵大