【问题标题】:CUDA optimization for a vector tensor product using a custom kernel or CUBLAS使用自定义内核或 CUBLAS 对向量张量积进行 CUDA 优化
【发布时间】:2021-08-12 05:58:06
【问题描述】:

我有两个向量 ab。每个向量包含一个 3d 点的坐标xyzvector3f

struct Vector3f
{ 
    float x;
    float y;
    float z;
}

向量a 的大小为n = 5000 点,向量b 的大小为m = 4000。我需要在它们之间做一个张量向量积,就像在图片的右侧一样。结果向量的长度大小应为5000 * 4000,并包含将结果存储在c 的浮点数。

__global__ void tensor3dProdcutClassic(const int n, const int m, const Vector3f *a, const Vector3f *b, float *c) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    // int j = blockIdy.y * blockDim.y + threadIdx.y;

    //check if  the idx is out of range
    if (i < n) {
        for (int j = 0; j < m; j++) {
            int idx = j + m * i;
            c[idx] = a[i].x * b[j].x + a[i].y * b[j].y + a[i].z * b[j].z;
        } 
    }
} 

dim3 blockSize(32, 1, 1);
dim3 gridSize((n + blockSize.x - 1) / blockSize.x, 1, 1);

tensor3dProdcutClassic<<<gridSize, blockSize>>>(n, m, x, y, out);

我在 Volta 拱门上得到了很长的执行时间。
我的问题是如何优化内核以减少时间,这主要是因为内核内部的 for 循环。我知道这里所有的全局读写都没有合并。

【问题讨论】:

  • 如果你想获得高性能,那么使用数组结构是一个非常糟糕的主意,尤其是在基于 SIMT 模型的 GPU 上。请注意,由于字段数不能被 2 的幂整除,因此对齐可能会成为问题。
  • 谢谢你,理查德,你是对的。我已将 Vector3f 更改为 typedef float Point3d[3];但我仍然得到很高的计算时间
  • 问题不在于结构本身,而在于内存布局。尽管 3 个浮点数的数组在可能的填充与否方面可能会更好,但它仍然会导致低效的交错读取。我正在考虑拥有 3 个普通的大数组 (x + y + z)。
  • 同意,定义 3 个普通数组或将它们存储在以行优先顺序的矩阵中可以提供更好的性能并提供有效的交错读取。我将修改代码。然而,主要问题是内核实现没有优化,因此速度很慢。

标签: c++ cuda gpu


【解决方案1】:

您可以让内核同时通过ab,如下所示:

__global__ void tensor3dProdcutClassic(const int n, const int m, const Vector3f *a, const Vector3f *b, float *c)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    int j = blockIdy.y * blockDim.y + threadIdx.y;

    if (i < n && j < m)
    {
        int idx = j + m * i;
        c[idx] = a[i].x * b[j].x + a[i].y * b[j].y + a[i].z * b[j].z;
    }
}

dim3 blockSize(32, 32);
dim3 gridSize((int)ceil(n / 32.0), (int)ceil(m / 32.0));

tensor3dProdcutClassic<<<gridSize, blockSize>>>(n, m, x, y, out);

更新
我尝试修改代码以使用有和没有共享内存的单个数组,没有共享内存的代码总是快 3 或 4 倍。

使用共享内存:

#define BLOCK_SIZE 32
void tensor3dProdcut(const int n, const int m, const float* a, const float* b, float* c)
{
    float* d_a;
    size_t size = (uint64_t)n * 3 * sizeof(float);
    cudaMalloc(&d_a, size);
    cudaMemcpy(d_a, a, size, cudaMemcpyHostToDevice);
    float* d_b;
    size = (uint64_t)m * 3 * sizeof(float);
    cudaMalloc(&d_b, size);
    cudaMemcpy(d_b, b, size, cudaMemcpyHostToDevice);
    float* d_c;
    size = (uint64_t)n * m * sizeof(float);
    cudaMalloc(&d_c, size);
    dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);
    dim3 dimGrid((int)ceil((double)n / BLOCK_SIZE), (int)ceil((double)m / BLOCK_SIZE));
    tensor3dProdcutKernel<<<dimGrid, dimBlock>>>(d_a, d_b, d_c, n, m);
    cudaMemcpy(c, d_c, size, cudaMemcpyDeviceToHost);
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);
}

__global__ void tensor3dProdcutKernel(float* a, float* b, float* c, int n, int m)
{
    int i, blockRow, blockCol, row, col;
    float Cvalue;
    blockRow = blockIdx.x;
    blockCol = blockIdx.y;
    row = threadIdx.x;
    col = threadIdx.y;
    if (blockRow * BLOCK_SIZE + row >= n || blockCol * BLOCK_SIZE + col >= m)
        return;
    __shared__ double as[BLOCK_SIZE][3];
    __shared__ double bs[BLOCK_SIZE][3];
    for (i = 0; i < 3; i++)
    {
        as[row][i] = a[(BLOCK_SIZE * blockRow + row) * 3 + i];
        bs[col][i] = b[(BLOCK_SIZE * blockCol + col) * 3 + i];
    }
    __syncthreads();
    Cvalue = 0;
    for (i = 0; i < 3; i++)
        Cvalue += as[row][i] * bs[col][i];
    c[(BLOCK_SIZE * blockRow + row) * m + BLOCK_SIZE * blockCol + col] = Cvalue;
}

没有共享内存:

__global__ void tensor3dProdcutKernel(float* a, float* b, float* c, int n, int m)
{
    int i, blockRow, blockCol, row, col;
    float Cvalue;
    blockRow = blockIdx.x;
    blockCol = blockIdx.y;
    row = threadIdx.x;
    col = threadIdx.y;
    if (blockRow * BLOCK_SIZE + row >= n || blockCol * BLOCK_SIZE + col >= m)
        return;
    Cvalue = 0;
    for (i = 0; i < 3; i++)
        Cvalue += a[(BLOCK_SIZE * blockRow + row) * 3 + i] * b[(BLOCK_SIZE * blockCol + col) * 3 + i];
    c[(BLOCK_SIZE * blockRow + row) * m + BLOCK_SIZE * blockCol + col] = Cvalue;
}

【讨论】:

  • 谢谢,我也尝试过使用 2d 内核,它将时间减少到 60 毫秒。但是,它仍然非常昂贵。我也尝试过网格步幅,但仍然相同。我看到的问题是内核没有优化并且不能确保所有全局读写都被合并。我相信,如果我们避免共享内存中的银行冲突,那么使用 Cuda 共享内存是关键。
  • @Arrfou 你可以使用这个共享内存的例子。 docs.nvidia.com/cuda/cuda-c-programming-guide/…
  • 好的,我会尝试修改代码并使用共享内存。内核性能真的很慢,因为对结果数组 c 的访问是跨步的(块中的每个线程正在写入与前一个线程相隔一整行的位置),即使在 a 和 b 之间,读取和写入也没有合并
  • 在我们找到解决方案之前我不会关闭这个帖子。
  • @Arrfou 你可以试试。但根据我在共享内存方面的经验,它并不总是能提供理想的结果。
猜你喜欢
  • 1970-01-01
  • 2021-02-07
  • 2013-09-02
  • 2011-10-22
  • 1970-01-01
  • 1970-01-01
  • 2018-01-07
  • 1970-01-01
  • 2014-03-14
相关资源
最近更新 更多