【问题标题】:Matrix multiplication in CUDA running out of memoryCUDA中的矩阵乘法内存不足
【发布时间】:2017-10-12 06:01:30
【问题描述】:

我尝试使用脚本计算矩阵乘法:

import numpy as np
import math
from timeit import default_timer as timer
from numba import cuda
from numba import *


def mult(a,b):
    return a*b
mult_gpu=cuda.jit(restype=float32,argtypes=[float32,float32],device=True)(mult)

@cuda.jit(argtypes=[float32[:,:],float32[:,:],float32[:,:,:]])
def mult_kernel(a,b,c):
    Ni=c.shape[0]
    Nj=c.shape[1]
    Nk=c.shape[2]

    startX,startY,startZ=cuda.grid(3)
    gridX=cuda.gridDim.x*cuda.blockDim.x
    gridY=cuda.gridDim.y*cuda.blockDim.y
    gridZ=cuda.gridDim.z*cuda.blockDim.z

    for i in range(startX,Ni,gridX):
        for j in range(startY,Nj,gridY):
            for k in range(startZ,Nk,gridZ):
                c[i,j,k]=mult_gpu(a[i,k],b[j,k])

def main():
    A=np.ones((20,50000),dtype=np.float32)
    B=np.ones((3072,50000),dtype=np.float32)
    C=np.ones((20,3072,50000),dtype=np.float32)
    (Ni,Nj,Nk)=C.shape

    my_gpu=cuda.get_current_device()
    thread_ct=my_gpu.WARP_SIZE
    block_ct_x=int(math.ceil(float(Ni)/thread_ct))
    block_ct_y=int(math.ceil(float(Nj)/thread_ct))
    block_ct_z=int(math.ceil(float(Nk)/thread_ct))

    blockdim=thread_ct,thread_ct,thread_ct
    griddim=block_ct_x,block_ct_y,block_ct_z
    print "Threads per block:",blockdim
    print "Blocks per grid:",griddim

    start=timer()
    Cg=cuda.to_device(C)
    mult_kernel[griddim,blockdim](A,B,Cg)
    Cg.to_host()
    dt=timer()-start
    print "Computation done in %f s"%(dt)

    print 'C[:3,1,1] = ',C[:3,1,1]
    print 'C[-3:,1,1] = ',C[-3:,1,1]


if __name__=='__main__':
    main()

执行此操作会产生错误:

numba.cuda.cudadrv.driver.CudaAPIError: [2] Call to cuMemAlloc results in CUDA_ERROR_OUT_OF_MEMORY

我该如何解决这个内存问题?

尽管如此,使用更小的矩阵

    A=np.ones((20,500),dtype=np.float32)
    B=np.ones((372,500),dtype=np.float32)
    C=np.ones((20,372,500),dtype=np.float32)

还是有错误:

numba.cuda.cudadrv.driver.CudaAPIError: [1] Call to cuLaunchKernel results in CUDA_ERROR_INVALID_VALUE

我受到Mandelbrot Example 的启发来实现上面的计算。

EDIT1

为了解决任何混淆,这实际上是一个 3D 矩阵乘以 3D 矩阵:

    A=np.ones((20,1,50000),dtype=np.float32)
    B=np.ones((1,3072,50000),dtype=np.float32)
    C=np.ones((20,3072,50000),dtype=np.float32)

我跳过了AB 中的一个维度,因为计算不需要它。

EDIT2

我的 GPU 是:

    In [1]: from numba import cuda

    In [2]: gpu=cuda.get_current_device()

    In [3]: gpu.name
    Out[3]: 'GeForce GT 750M'

EDIT3

根据我的GPU的内存(2GB),我将每个维度的大小缩小了2:

    dimx=10
    dimy=1536
    dimz=25000
    A=np.ones((dimx,dimz),dtype=np.float32)
    B=np.ones((dimy,dimz),dtype=np.float32)
    C=np.ones((dimx,dimy,dimz),dtype=np.float32)

但我仍然收到CUDA_ERROR_OUT_OF_MEMORY 错误。这怎么解释?

计算得出 3 个矩阵的大小约为 1.7GB: (10*1536*25000*4.+10*25000*4+1536*25000*4.)/(10**9)=1.6906

【问题讨论】:

    标签: python cuda gpu numba


    【解决方案1】:

    关于第一个问题,您的内存不足。造成这种情况的一个主要因素是,这不是人们通常进行矩阵-矩阵乘法的方式。通常,当您将行和列元素相乘时,您会保持一个运行总和,然后将该总和存储在乘积(结果)矩阵中的适当位置。这将允许您为c 矩阵提供更小的尺寸,即。它只需要 2 维,而不是 3 维。您可能希望只研究矩阵-矩阵乘法的线性代数定义。当您将 2D 矩阵乘以 2D 矩阵时,结果是 2D 矩阵,而不是 3D 矩阵。

    简而言之,是这样的:

    for i in range(startX,Ni,gridX):
        for j in range(startY,Nj,gridY):
            c[i,j] = 0
            for k in range(startZ,Nk,gridZ):
                c[i,j]= c[i,j] + mult_gpu(a[i,k],b[j,k])
    

    并相应地调整您的 c 形状。

    如果您确实需要 3D 形式的单个产品,就像您在这里所做的那样,那么我无话可说,除了您需要缩放问题以适应您使用的任何 GPU 的 GPU 内存大小。

    关于第二个问题,你这里有个问题:

    thread_ct=my_gpu.WARP_SIZE
    ...
    
    blockdim=thread_ct,thread_ct,thread_ct
    

    WARP_SIZE 是 32(大概),所以您要求一个 3D 块,尺寸为 32*32*32 = 32K 线程。但是 CUDA 线程块被限制为最多 1024 个线程,这个限制是各个维度的乘积。

    如果你把你的thread_ct改为8,例如:

    thread_ct=8
    

    您应该能够解决这个特定问题。

    【讨论】:

    • 感谢您澄清第二个问题!关于矩阵乘法,它实际上是 3D 乘 3D 矩阵乘法。我只是跳过了一个维度,因为它不是计算所必需的。我对此添加了一个编辑。您能否解释一下“将问题缩放以适应 GPU”是什么意思?
    • 您的 ABC 数组都需要适合 GPU 内存。您可以通过运行deviceQuery CUDA 示例代码了解您的 GPU 有多少内存。例如,C=np.ones((20,3072,50000),dtype=np.float32) 的输出数组需要 20*3072*50000*4 字节(float32 = 4 字节)。仅该大小的一个阵列将占用大约 12GB。例如,如果您的 GPU 有 8GB,则您需要减小问题的大小,直到所有 3 个矩阵 A、B、C 都可以放入 8GB 中。事实上,您的 GT750m 可能有 4GB 内存(或更少,可能)。
    • 根据我的系统报告NVIDIA GeForce GT 750M 2048 MB它只有2GB。因此,我必须将每个矩阵分成 7 个部分:(20*3072*50000*4.+20*50000*4.+3072*50000*4.)/(2*10**9)=6.4532。然后运行内核函数mult_kernel 7 次。请纠正我,如果这是错误的。非常感谢,现在我知道我需要进一步调查的地方了。
    • 是的,尽管为了简单起见,我可能会将其分成 8 个部分,而不是 7 个。我曾一度认为这个问题是一个“学习练习”,因为在 SO 上有很多这样的问题。但是,我有点怀疑,从性能的角度来看,您所追求的这条途径是否会很有趣。如果这只是一个学习练习 - 太好了,试试吧。如果您这样做是因为您想加快速度,那么您可能会感到失望。
    • 嗯,实际上两者兼而有之。我想了解如何使用 CUDA,看看我是否可以加快 CPU 上大约需要 30 秒的计算。但是,我仍然没有解决内存问题,即使通过将矩阵的每个维度减少 2,我在上面进行了编辑。我还缺少什么吗?
    猜你喜欢
    • 2019-07-23
    • 2020-12-22
    • 2021-07-08
    • 2012-05-06
    • 2011-04-21
    • 1970-01-01
    • 2011-09-07
    • 2012-12-09
    • 1970-01-01
    相关资源
    最近更新 更多