【问题标题】:CUDA kernel 2x2 ZgemmBatched: 5x faster then CuBLAS. Can it go faster?CUDA 内核 2x2 ZgemmBatched:比 CuBLAS 快 5 倍。能不能快点?
【发布时间】:2015-04-22 14:51:06
【问题描述】:

我发现在使用小矩阵时,CuBLAS API 的性能很差,尤其是批量通用矩阵乘法。我的目标是编写一个快速内核来计算 2x2 矩阵大小的批量复数双矩阵乘法。内核应该能够从更大的方阵中提取这些矩阵,即块矩阵乘法。

我编写的内核每块计算 8 个 2x2 双复数矩阵乘法(32 个线程)。我可以在 GTX 970(计算能力 5.2)上实现大约 1.65 双 GFlops(NSight 分析),它应该执行 109 双 GFlops。计算 910 万次矩阵乘法大约需要 300ms。

*a*b*c 是指向 N 次 nxn 矩阵数组的指针。这些矩阵必须是平方的。

ldaldbldc 是所有输入矩阵的主要维度。使用这些参数,任何方阵数组都可用于提取 2x2 矩阵。

问题:如何提高此内核的性能?

#define THREADMULTIPLIER 8

__global__ void bulkMatrixMul(const cuDoubleComplex *a, int lda, const cuDoubleComplex *b, int ldb, cuDoubleComplex *c, int ldc){
    int pos = (THREADMULTIPLIER * blockIdx.x + threadIdx.z);
    int ia = pos * lda * lda + threadIdx.x;
    int ib = (pos * ldb + threadIdx.y) * ldb;
    c[(pos * ldc + threadIdx.y) * ldc + threadIdx.x] = cuCfma(a[ia], b[ib], cuCmul(a[ia + LD], b[ib + 1]));
};


void bulkBatchedMatrixMul(complex<double> *a, int lda, complex<double> *b, int ldb, complex<double> *c, int ldc, int batchSize, cudaStream_t *stream){
    dim3 threads(2, 2, THREADMULTIPLIER);
    bulkMatrixMul << <batchSize / THREADMULTIPLIER, threads, 0, *stream >> >((cuDoubleComplex*)a, lda, (cuDoubleComplex*)b, ldb, (cuDoubleComplex*)c , ldc);
    if (cudaSuccess != cudaGetLastError())
        printf("Error!\n");
}

通过分析 Christian Sarofeen 的代码,我的内核速度提高了 10 倍:

  1. 如果您将总和作为一个整体而不是使用 += 和 -= 写入会更快。
  2. 声明输出,尤其是当您拆分输出的实部和虚部计算时会提高速度。

下面的内核是一个优化的内核,每个块计算 16 个矩阵乘法。它可以在 31 毫秒内计算 910 万次矩阵乘法:我使用 10000 的 batchSize 并在 13 个流中每个流调用这个内核 70 次。根据 NSight 的说法,它实现了大约 15 双 GFlops。使用了 GTX970。

__global__ void bulkMatrixMul(const cuDoubleComplex *a, int lda, const cuDoubleComplex *b, int ldb, cuDoubleComplex *c, int ldc){
    int pos = (blockDim.z * blockIdx.x + threadIdx.z);
    int ia = pos * lda * lda + threadIdx.x;
    int ib = (pos * ldb + threadIdx.y) * ldb;
    cuDoubleComplex cR;
    cR.x = a[ia].x * b[ib].x - a[ia].y * b[ib].y - a[ia + lda].y * b[ib + 1].y + a[ia + lda].x * b[ib + 1].x;
    cR.y = a[ia].x * b[ib].y + a[ia].y * b[ib].x + a[ia + lda].x * b[ib + 1].y + a[ia + lda].y * b[ib + 1].x;
    c[(pos * ldc + threadIdx.y) * ldc + threadIdx.x] = cR;
};


void bulkBatchedMatrixMul(complex<double> *a, int lda, complex<double> *b, int ldb, complex<double> *c, int ldc, int batchSize, cudaStream_t *stream){
    dim3 threads(2, 2, 16);
    bulkMatrixMul <<< batchSize / 16, threads, 0, *stream >>>((cuDoubleComplex*)a, lda, (cuDoubleComplex*)b, ldb, (cuDoubleComplex*)c , ldc);
    if (cudaSuccess != cudaGetLastError())
        printf("Error!\n");
}

【问题讨论】:

  • 澄清一下,您想将多个 2x2 双复矩阵相乘?
  • 是的,我在 13 个流中将 10 000 个矩阵批量相乘 70 次,结果是 910 万次矩阵相乘。
  • a和b是如何存储的?
  • 一个 Nnn 大小的内存块:A0A1A2A3...AN,每个矩阵都是列优先的。
  • 你的问题和目标不清楚。

标签: c++ matrix cuda cublas


【解决方案1】:

我已经使用 Christian Sarofeen 的代码调整了我的内核。他的内核需要 60 毫秒才能在 GTX 970 上计算 910 万次矩阵乘法。

我调整后的内核可以在 31 毫秒内完成。谢谢克里斯蒂安!

__global__ void bulkMatrixMul(const cuDoubleComplex *a, int lda, const cuDoubleComplex *b, int ldb, cuDoubleComplex *c, int ldc){
    int pos = (blockDim.z * blockIdx.x + threadIdx.z);
    int ia = pos * lda * lda + threadIdx.x;
    int ib = (pos * ldb + threadIdx.y) * ldb;
    cuDoubleComplex cR;
    cR.x = a[ia].x * b[ib].x - a[ia].y * b[ib].y - a[ia + lda].y * b[ib + 1].y + a[ia + lda].x * b[ib + 1].x;
    cR.y = a[ia].x * b[ib].y + a[ia].y * b[ib].x + a[ia + lda].x * b[ib + 1].y + a[ia + lda].y * b[ib + 1].x;
    c[(pos * ldc + threadIdx.y) * ldc + threadIdx.x] = cR;
};


void bulkBatchedMatrixMul(complex<double> *a, int lda, complex<double> *b, int ldb, complex<double> *c, int ldc, int batchSize, cudaStream_t *stream){
    dim3 threads(2, 2, 16);
    bulkMatrixMul << <batchSize / 16, threads, 0, *stream >> >((cuDoubleComplex*)a, lda, (cuDoubleComplex*)b, ldb, (cuDoubleComplex*)c , ldc);
    if (cudaSuccess != cudaGetLastError())
        printf("Error!\n");
}

【讨论】:

  • 我不明白你在用 pos、ia 和 ib 做什么或你的 LD 来自哪里,但我肯定会验证该代码,因为有些地方看起来不太正确。跨度>
  • *a` , *b*c 都指向 N 次方矩阵的数组,其前导维度(col 的大小)为 ldaldbldc分别。 pos 计算数组中需要计算哪些矩阵。 iaorìindex_a 计算需要计算的 A 的第一个元素的准确位置。 ib 也一样。我提醒你,每个线程块同时计算 16 个矩阵乘法:x & y 为一次乘法,z 定义哪个矩阵。我已经更正了LD,它必须是lda
  • 通过将所有 cR.x 和 cR.y 和添加到一个方程中,我将速度从 38 毫秒提高到 31 毫秒。代码已修改。
  • 您是否验证过这会为您提供正确的结果?
  • 是的,我得到了正确的结果。我已经用一组独特的输入对其进行了测试。
【解决方案2】:

我会为每个矩阵乘法分配一个线程,并将所有内容转储到寄存器中。不要担心占用,因为我怀疑这将受到内存限制。一旦进入寄存器,每个线程将通过矩阵乘法来做一个矩阵并将其存储在 c 中。下面是一个演示。

#include <stdio.h>
#include <iostream>

#include <cuComplex.h>
#include <cuda.h>
#include <cuda_runtime.h>

#define N_MATRIX 10000

using namespace std;

__device__ void D_C_Mult_Add(cuDoubleComplex &a, cuDoubleComplex &b, cuDoubleComplex &c){
        c.x+=a.x*b.x;
        c.y+=a.x*b.y;
        c.x-=a.y*b.y;
        c.y+=a.y*b.x;
}

__global__ void multMatrix(cuDoubleComplex* a, cuDoubleComplex* b, cuDoubleComplex* c) {
    int tid = blockIdx.x*blockDim.x + threadIdx.x;
    int stride=gridDim.x*blockDim.x;
    if(tid>=N_MATRIX) return;
    cuDoubleComplex a_sub[4], b_sub[4], c_sub[4];

    for(int i=0; i<4; i++){
        a_sub[i]=a[tid+stride*i];
        b_sub[i]=a[tid+stride*i];
        c_sub[i].x=0.0;
        c_sub[i].y=0.0;
    }

    D_C_Mult_Add(a_sub[0], b_sub[0], c_sub[0]);
    D_C_Mult_Add(a_sub[0], b_sub[1], c_sub[1]);
    D_C_Mult_Add(a_sub[2], b_sub[0], c_sub[2]);
    D_C_Mult_Add(a_sub[2], b_sub[1], c_sub[3]);

    D_C_Mult_Add(a_sub[1], b_sub[2], c_sub[0]);
    D_C_Mult_Add(a_sub[1], b_sub[3], c_sub[1]);
    D_C_Mult_Add(a_sub[3], b_sub[2], c_sub[2]);
    D_C_Mult_Add(a_sub[3], b_sub[3], c_sub[3]);

    for(int i=0; i<4; i++)
        c[tid+stride*i]=c_sub[i];

}

int main(int argc, char **argv) {



    cuDoubleComplex *a, *b, *c;
    cudaMalloc(&a, sizeof(cuDoubleComplex)*N_MATRIX*4);
    cudaMalloc(&b, sizeof(cuDoubleComplex)*N_MATRIX*4);
    cudaMalloc(&c, sizeof(cuDoubleComplex)*N_MATRIX*4);

    multMatrix<<<N_MATRIX/128+1, 128>>>(a, b, c);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-03
    • 1970-01-01
    • 2018-03-02
    • 2013-05-02
    • 1970-01-01
    • 2012-08-16
    • 2012-04-26
    • 1970-01-01
    相关资源
    最近更新 更多