我注意到了cublasSgemmStridedBatched的一些奇怪的表现,我正在寻找解释。矩阵大小固定为20x20。以下是几种不同批量大小的时序(仅限乘法,无数据传输):
前几个批量大小如我所料,随着批量大小增加十倍,时间线性增加。然而,使用100,000个矩阵突然发生了3.4倍的加速?
如果矩阵大小固定为10x10并且再次执行试验,我会发现:
同样,在100,000批量大小时出现惊人的22倍速度?让我想知道为什么1,000和10,000的批量大小比批量大小100,000更慢,因为矩阵大小仍然是10x10。
不同的算法是否用于不同的批量大小?这个表现我觉得很奇怪。当我使用cublasSgemmBatched进行此试验时,会发生类似的结果。 这些试验在GeForce GTX 1080 Ti上执行。提供最小的工作代码:
#include <stdio.h>
#include <stdlib.h>
#include "math.h"
#include "cublas_v2.h"
//nvcc -lcublas cublas.c -o cublas.out
int main(int argc, char* argv[])
{
int i,j,k,index;
// Linear dimension of matrices
int dim = 20;
int batch_count = 10*10*10*10*10*1;
// Allocate host storage for batch_count A,B,C square matrices
float* h_A = malloc(sizeof(float) * dim * dim * batch_count);
float* h_B = malloc(sizeof(float) * dim * dim * batch_count);
float* h_C = malloc(sizeof(float) * dim * dim * batch_count);
for(k=0; k<batch_count; k++) {
for(j=0; j<dim; j++) {
for(i=0; i<dim; i++) {
index = i*dim + j + k*dim*dim;
h_A[index] = index*index + 0.0f;
h_B[index] = index + 1.0f;
h_C[index] = 0.0f;
}
}
}
float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, sizeof(float) * dim * dim * batch_count);
cudaMalloc(&d_B, sizeof(float) * dim * dim * batch_count);
cudaMalloc(&d_C, sizeof(float) * dim * dim * batch_count);
cudaMemcpy(h_A,d_A,sizeof(float) * dim * dim * batch_count,cudaMemcpyDeviceToHost);
cudaMemcpy(h_B,d_B,sizeof(float) * dim * dim * batch_count,cudaMemcpyDeviceToHost);
cudaMemcpy(h_C,d_C,sizeof(float) * dim * dim * batch_count,cudaMemcpyDeviceToHost);
cublasHandle_t handle;
cublasCreate(&handle);
// Do the actual multiplication
float time_cuda_event;
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop) ;
cudaEventRecord(start, 0);
float alpha = 1.0f; float beta = 1.0f;
cublasSgemmStridedBatched(handle,
CUBLAS_OP_N,
CUBLAS_OP_N,
dim, dim, dim,
&alpha,
(const float*)d_A, dim,
dim*dim,
(const float*)d_B, dim,
dim*dim,
&beta,
d_C, dim,
dim*dim,
batch_count);
( cudaEventRecord(stop, 0) );
( cudaEventSynchronize(stop) );
( cudaEventElapsedTime(&time_cuda_event, start, stop) );
printf("Time : %3.1f ms \n", time_cuda_event);
cudaMemcpy(h_C,d_C,sizeof(float) * dim * dim * batch_count,cudaMemcpyDeviceToHost);
// Destroy the handle
cublasDestroy(handle);
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
free(h_A);
free(h_B);
free(h_C);
return 0;
}
答案 0 :(得分:1)
这似乎只是CUBLAS中启发式的结果。如果我运行代码的修改(和工作)版本,我会得到5x5案例的时间:
not
分析显示,在最多包含10000个条目的批处理的情况下,该库运行一个内核:
Batch size : 10 Time : 0.019104 ms
Batch size : 100 Time : 0.038304 ms
Batch size : 1000 Time : 0.163520 ms
Batch size : 10000 Time : 1.410944 ms
Batch size : 100000 Time : 1.614144 ms
Batch size : 1000000 Time : 16.057407 ms
当较大的大小时,它会运行多个调用另一个内核来为该调用提供服务:
1.10759s 16.831us (1 1 10) (128 1 1) 120 12.250KB 0B - - - - GeForce GTX 970 1 7 maxwell_sgemm_128x64_nn [3939]
1.10766s 19.168us (1 1 100) (128 1 1) 120 12.250KB 0B - - - - GeForce GTX 970 1 7 maxwell_sgemm_128x64_nn [3971]
1.10773s 147.71us (1 1 1000) (128 1 1) 120 12.250KB 0B - - - - GeForce GTX 970 1 7 maxwell_sgemm_128x64_nn [4003]
1.10791s 1.4064ms (1 1 10000) (128 1 1) 120 12.250KB 0B - - - - GeForce GTX 970 1 7 maxwell_sgemm_128x64_nn [4035]
您观察到的不一致似乎是由库中一个内核到另一个内核的更改引起的,这可能是由某些批量大小标准造成的。您可以看到两个内核似乎每个批处理项使用一个块,内核使用较大的大小使用具有256个线程的2D块,而较小的内核使用具有128个线程的1D块。除此之外,性能差异还取决于内部实现细节。尽管这样做可能违反了最终用户许可,但如果您想了解更多内容,则需要反汇编内核并查看它们的工作原理。该工具包包含执行此操作所需的所有工具,但我不建议您这样做。