为什么 Apple 的金属矩阵乘法示例需要填充 C
Why Apple's Metal Matrix Multiplication Example needs padding C
我正在学习 Apple Metal 尝试进行一些 GPU 计算。
我查看了苹果给的matrix multiplication example。有一点想不明白
// Number of rows in matrices A and C.
@property (nonatomic) uint16_t m;
// Number of columns in matrix A; number of rows in matrix B.
@property (nonatomic) uint16_t n;
// Number of columns in matrices B and C.
@property (nonatomic) uint16_t k;
// Output matrix (padded) C row count
@property (nonatomic, readonly) uint16_t M;
// Output matrix (padded) C column count
@property (nonatomic, readonly) uint16_t K;
// Output matrix C = A x B
@property (nonatomic, readonly) float* output;
它说矩阵C被填充了。我不清楚 pad
在这里是什么意思。这是某种对齐方式吗?因为我知道 Metal 的着色器语言规范中有类型对齐,但我不知道为什么我们需要在这里填充缓冲区。
谢谢。
它与优化内存访问有关。您的 GPU 有许多线程组,每个线程组包含相对少量的专用内存(几 KB),可以非常快速地访问。这与 GPU 的主内存是分开的,后者可能是几 GB 的相对较慢的内存。
因为所有 3 个矩阵(A
、B
和 C
)不太可能都适合单个线程组的内存,并且回退到循环内的主内存将非常困难慢,我们将计算分成 "blocks" 或扇区。想象一下将结果矩阵 C
分成一个网格,其中每个扇区都是 8 x 8 元素的集合:然后我们可以指示线程组 1 计算左上角扇区的结果,而其他线程组同时计算其他扇区.在这种情况下,线程组 1 只需要 A
的前 8 行和 B
的前 8 列来计算它在 C
中的部分。这意味着我们可以向线程组 1 发送更少量的数据,使其保持在缓存限制内。
Metal 要求我们填充矩阵的原因是它可以将 C
划分为完美的网格。如果你的真实结果矩阵是 12 x 18,扇区大小是 8 x 8,这意味着 C
是 1.5 x 2.25 个扇区。 GPU 无法在部分扇区上高效运行,因此您必须用零填充矩阵才能达到整数 - 在本例中为 2 x 3 扇区或 16 x 24 元素。您牺牲了一点存储空间和更多的时钟周期来进行高度优化的并行处理。
我正在学习 Apple Metal 尝试进行一些 GPU 计算。
我查看了苹果给的matrix multiplication example。有一点想不明白
// Number of rows in matrices A and C.
@property (nonatomic) uint16_t m;
// Number of columns in matrix A; number of rows in matrix B.
@property (nonatomic) uint16_t n;
// Number of columns in matrices B and C.
@property (nonatomic) uint16_t k;
// Output matrix (padded) C row count
@property (nonatomic, readonly) uint16_t M;
// Output matrix (padded) C column count
@property (nonatomic, readonly) uint16_t K;
// Output matrix C = A x B
@property (nonatomic, readonly) float* output;
它说矩阵C被填充了。我不清楚 pad
在这里是什么意思。这是某种对齐方式吗?因为我知道 Metal 的着色器语言规范中有类型对齐,但我不知道为什么我们需要在这里填充缓冲区。
谢谢。
它与优化内存访问有关。您的 GPU 有许多线程组,每个线程组包含相对少量的专用内存(几 KB),可以非常快速地访问。这与 GPU 的主内存是分开的,后者可能是几 GB 的相对较慢的内存。
因为所有 3 个矩阵(A
、B
和 C
)不太可能都适合单个线程组的内存,并且回退到循环内的主内存将非常困难慢,我们将计算分成 "blocks" 或扇区。想象一下将结果矩阵 C
分成一个网格,其中每个扇区都是 8 x 8 元素的集合:然后我们可以指示线程组 1 计算左上角扇区的结果,而其他线程组同时计算其他扇区.在这种情况下,线程组 1 只需要 A
的前 8 行和 B
的前 8 列来计算它在 C
中的部分。这意味着我们可以向线程组 1 发送更少量的数据,使其保持在缓存限制内。
Metal 要求我们填充矩阵的原因是它可以将 C
划分为完美的网格。如果你的真实结果矩阵是 12 x 18,扇区大小是 8 x 8,这意味着 C
是 1.5 x 2.25 个扇区。 GPU 无法在部分扇区上高效运行,因此您必须用零填充矩阵才能达到整数 - 在本例中为 2 x 3 扇区或 16 x 24 元素。您牺牲了一点存储空间和更多的时钟周期来进行高度优化的并行处理。