注意:我的问题是关于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]
,那么该值应该可用。我知道数据通常被分解以适应线程组缓存 - 如果是这种情况,它遵循什么规则以及如何解释它?
答案 0 :(得分:2)
我的理解是
gid
是C
中单个元素的位置:gid.x
是列,gid.y
是行。如果不是这样,有人请指正。
这不是真的。 gid
是网格中的位置。
因为网格实际上是64x16,所以将调用计算函数用于超出8x8矩阵(A
和C
)和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
将始终为方形,因此可以针对单个值检查宽度和高度。)
您可以尝试调整C
和threadsPerThreadgroup
的计算,以使网格与矩阵的大小完全相同,但对于所有情况而言,这可能会非常繁琐。也就是说,你当然可以让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,再次大于矩阵。