Python 将矩阵复制到主机所需的时间增加了使用矩阵的次数
我正在使用PyCUDA、CUDAMat和Numba对GPU矩阵乘法进行基准测试,遇到了一些我无法解释的行为。Python 将矩阵复制到主机所需的时间增加了使用矩阵的次数,python,cuda,benchmarking,pycuda,Python,Cuda,Benchmarking,Pycuda,我正在使用PyCUDA、CUDAMat和Numba对GPU矩阵乘法进行基准测试,遇到了一些我无法解释的行为。 我独立计算了3个不同步骤所需的时间-将2个矩阵发送到设备内存,计算点积,并将结果复制回主机内存。 点积步骤的基准测试是在一个循环中完成的,因为我的应用程序将在返回结果之前执行许多乘法 随着循环次数的增加,点积时间会像预期的那样线性增加。但我不能理解的是,将最终结果发送回主机内存所需的时间也会随着循环计数的增加而线性增加,即使它只是将一个矩阵复制回主机内存。无论执行多少个矩阵乘法循环,结果
我独立计算了3个不同步骤所需的时间-将2个矩阵发送到设备内存,计算点积,并将结果复制回主机内存。
点积步骤的基准测试是在一个循环中完成的,因为我的应用程序将在返回结果之前执行许多乘法 随着循环次数的增加,点积时间会像预期的那样线性增加。但我不能理解的是,将最终结果发送回主机内存所需的时间也会随着循环计数的增加而线性增加,即使它只是将一个矩阵复制回主机内存。无论执行多少个矩阵乘法循环,结果的大小都是恒定的,因此这毫无意义。它的行为就像返回最终结果需要在循环的每个步骤返回所有中间结果一样 需要注意的一些有趣的事情是,所需时间的增加有一个峰值。当我在循环中超过1000个点积时,复制最终结果所需的时间达到峰值。 另一件事是,如果在点积循环中,我重新初始化保存结果的矩阵,则此行为将停止,并且无论执行多少次乘法,回抄时间都是相同的。
例如—
for i in range(1000):
gc = gpuarray.empty((MATRIX_SIZE, MATRIX_SIZE), np.float32)
matrixmul(ga, gb, gc, grid=(MATRIX_SIZE // TILE_SIZE, MATRIX_SIZE // TILE_SIZE), block=(TILE_SIZE, TILE_SIZE, 1))
result = gc.get()
最后要注意的是,这种情况在PyCUDA和Numba身上都会发生,但在CUDAMat身上却不会发生。我可以进行一百万次的乘法运算,但检索最终结果仍然需要相同的时间。CUDAMat有一个内置的矩阵乘法,这可能是原因,但对于PyCUDA和Numba,我使用他们自己的文档中提供的矩阵乘法代码
这是我的PyCUDA代码
from __future__ import division
import numpy as np
from pycuda import driver, compiler, gpuarray, tools
import time
import pycuda.autoinit
kernel_code_template = """
__global__ void MatrixMulKernel(float *A, float *B, float *C)
{
const int wA = %(MATRIX_SIZE)s;
const int wB = %(MATRIX_SIZE)s;
// Block index
const int bx = blockIdx.x;
const int by = blockIdx.y;
// Thread index
const int tx = threadIdx.x;
const int ty = threadIdx.y;
// Index of the first sub-matrix of A processed by the block
const int aBegin = wA * %(BLOCK_SIZE)s * by;
// Index of the last sub-matrix of A processed by the block
const int aEnd = aBegin + wA - 1;
// Step size used to iterate through the sub-matrices of A
const int aStep = %(BLOCK_SIZE)s;
// Index of the first sub-matrix of B processed by the block
const int bBegin = %(BLOCK_SIZE)s * bx;
// Step size used to iterate through the sub-matrices of B
const int bStep = %(BLOCK_SIZE)s * wB;
// The element of the block sub-matrix that is computed
// by the thread
float Csub = 0;
// Loop over all the sub-matrices of A and B required to
// compute the block sub-matrix
for (int a = aBegin, b = bBegin;
a <= aEnd;
a += aStep, b += bStep)
{
// Shared memory for the sub-matrix of A
__shared__ float As[%(BLOCK_SIZE)s][%(BLOCK_SIZE)s];
// Shared memory for the sub-matrix of B
__shared__ float Bs[%(BLOCK_SIZE)s][%(BLOCK_SIZE)s];
// Load the matrices from global memory to shared memory
// each thread loads one element of each matrix
As[ty][tx] = A[a + wA * ty + tx];
Bs[ty][tx] = B[b + wB * ty + tx];
// Synchronize to make sure the matrices are loaded
__syncthreads();
// Multiply the two matrices together;
// each thread computes one element
// of the block sub-matrix
for (int k = 0; k < %(BLOCK_SIZE)s; ++k)
Csub += As[ty][k] * Bs[k][tx];
// Synchronize to make sure that the preceding
// computation is done before loading two new
// sub-matrices of A and B in the next iteration
__syncthreads();
}
// Write the block sub-matrix to global memory;
// each thread writes one element
const int c = wB * %(BLOCK_SIZE)s * by + %(BLOCK_SIZE)s * bx;
C[c + wB * ty + tx] = Csub;
}
"""
MATRIX_SIZE = 512
TILE_SIZE = 8
BLOCK_SIZE = TILE_SIZE
np.random.seed(100)
a_cpu = np.random.randn(MATRIX_SIZE, MATRIX_SIZE).astype(np.float32)
b_cpu = np.random.randn(MATRIX_SIZE, MATRIX_SIZE).astype(np.float32)
kernel_code = kernel_code_template % {
'MATRIX_SIZE': MATRIX_SIZE,
'BLOCK_SIZE': BLOCK_SIZE,
}
mod = compiler.SourceModule(kernel_code)
matrixmul = mod.get_function("MatrixMulKernel")
#copy to device memory
total = time.clock()
ga = gpuarray.to_gpu(a_cpu)
gb = gpuarray.to_gpu(b_cpu)
gc = gpuarray.empty((MATRIX_SIZE, MATRIX_SIZE), np.float32)
copy_to = time.clock() - total
#matrix multiplication
mult = time.clock()
for i in range(1000):
matrixmul(ga, gb, gc, grid=(MATRIX_SIZE // TILE_SIZE, MATRIX_SIZE // TILE_SIZE), block=(TILE_SIZE, TILE_SIZE, 1))
mult = time.clock() - mult
#copy result back to host memory
copy_from = time.clock()
res = gc.get()
copy_from = time.clock() - copy_from
total = time.clock() - total
#print out times for all 3 steps and the total time taken
print(copy_to)
print(mult)
print(copy_from)
print(total)
来自未来进口部的
将numpy作为np导入
从pycuda导入驱动程序、编译器、gpuarray、工具
导入时间
导入pycuda.autoinit
内核\u代码\u模板=”“”
__全局无效MatrixMulKernel(浮点*A、浮点*B、浮点*C)
{
const int wA=%(矩阵大小)s;
常量int wB=%(矩阵大小)s;
//块索引
常量int bx=blockIdx.x;
const int by=blockIdx.y;
//线程索引
const int tx=threadIdx.x;
const int ty=threadIdx.y;
//由块处理的第一个子矩阵的索引
常数int aBegin=wA*%(块大小)s*by;
//块处理的最后一个子矩阵的索引
常数int aEnd=aBegin+wA-1;
//步长用于迭代一个矩阵的子矩阵
常量int aStep=%(块大小)s;
//块处理的B的第一个子矩阵的索引
常量int bBegin=%(块大小)s*bx;
//用于迭代B的子矩阵的步长
常量int bStep=%(块大小)s*wB;
//计算的块子矩阵的元素
//按部就班
浮点数Csub=0;
//循环A和B的所有子矩阵,以
//计算块子矩阵
对于(inta=aBegin,b=bBegin;
aGPU内核启动是异步的。这意味着,您认为围绕for循环捕获的度量(乘法所需的时间)实际上并不是这样。它只是将内核启动发送到队列中所需的时间
实际内核执行时间被“吸收”到设备->主机复制时间的最终度量中(因为D->H复制强制所有内核在开始之前完成,并阻塞CPU线程)
关于“峰值”行为,当您向队列中启动足够多的内核时,它最终停止变为异步并开始阻塞CPU线程,因此您的“执行时间”测量值开始上升。这解释了峰值行为的变化
若要“修复”此问题,请在for循环之后和此行之前插入pycuda:
mult = time.clock() - mult
随着for循环的增加,您将看到执行时间增加,而D->H复制时间将保持不变。我曾考虑过类似的问题,但不确定如何搜索它。这非常有效。如果您想作为答案发布,我会接受它,如果有人使用Numba遇到同样的问题,您可以调用Numba.cuda.synchronize()