CUDA流的问题

时间:2011-09-05 03:11:59

标签: cuda cublas

我通过细分输入矩阵在一个GPU(Tesla C2050)上的不同流上运行CUBLAS v2.0(A [x / num_of_streams * y] B [x y] = C [ x / num_of_streams * y]),但不知何故,当我使用CUDA流时,它需要更多时间。以下是代码段:

             //plan is a struct containing the matrix dimensions and stream numbers
             //parallel in nstreams - should be! MAX 16 streams could run concurrently
            //Copy A - cudaMemCpyAsync
            for(i = 0; i < nstreams; i++)
                    cudgemm_copyA_in_streams (&plan[i]);
            //Copy B - cudaMemCpyAsync
            for(i = 0; i < nstreams; i++)
                    cudgemm_copyB_in_streams (&plan[i]);

            //Create handles - serial
            for(i = 0; i < nstreams; i++)
                    handle[i] = create_handle();

            //Run kernels - first doing a cublasSetStream(handle, plan->stream) before running cublasDgemm... 
            for(i = 0; i < nstreams; i++)
                    cudgemm_kernel_in_streams (&plan[i], handle[i], 1.0f, 1.0f);

            //Destroy handles - serial
            for(i = 0; i < nstreams; i++)
                    destroy_handle (handle[i]);

            //Copy C - cudaMemCpyAsync
            for(i = 0; i < nstreams; i++)
                    cudgemm_copyC_in_streams (&plan[i]);

            //EDIT: Function body

            //The other two copy functions are exactly the same as this
            void cudgemm_copyA_in_streams(TGPUplan *plan)
           {
                 cudasafe(cudaMemcpyAsync(plan->Ad_Data, plan->Ah_Data, (plan->Acols * plan->Arows * sizeof(double)), cudaMemcpyHostToDevice, plan->stream) );

            }

            //Create handle
            cublasHandle_t create_handle ()
            {
                   cublasHandle_t handle;
                   checkError(cublasCreate(&handle), "cublasCreate() error!\n");
                   return handle;
             }

             //Destroy handle
             void destroy_handle (cublasHandle_t handle)
             {
                  checkError(cublasDestroy(handle), "cublasDestroy() error!\n");
             }

             //Kernel
             void cudgemm_kernel_in_streams(TGPUplan *plan, cublasHandle_t handle, const double alpha, const double beta)
             {
                   cublasStatus_t ret;
                   cublasSetStream(handle, plan->stream);

                   ret = cublasDgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, plan->Arows, plan->Ccols, plan->Acols, &alpha, plan->Ad_Data, plan->Arows, plan->Bd_Data, plan->Brows, &beta, plan->Cd_Data, plan->Crows);
                   checkError(ret, "cublas Dgemm returned an error!\n");
              }

所以我在流之间来回跳转并分配工作,期望获得更好的执行时间,但我注意到流的数量越多,与不使用流的版本相比,程序花费的时间更多。我哪里错了? Nvidia论坛的交叉帖子 - http://forums.nvidia.com/index.php?showtopic=209420

编辑:

我修改了我的程序如下:

            //copy data
            for(i = 0; i < nstreams; i++)
            {
                    cudgemm_copyA_in_streams (&plan[i]);
                    cudgemm_copyB_in_streams (&plan[i]);
            }

            //Run kernel and copy back
            for(i = 0; i < nstreams; i++)
            {
                    cudgemm_kernel_in_streams (&plan[i], handle[i], 1.0f, 1.0f);
                    cudgemm_copyC_in_streams (&plan[i]);
            }

当我按照6144的矩阵顺序分析我的程序时,我得到以下信息:

Kernel time = 42.75 % of total GPU time 
Memory copy time = 28.9 % of total GPU time
Kernel taking maximum time = fermiDgemm_v2_kernel_val (42.8% of total GPU time)
Memory copy taking maximum time = memcpyHtoDasync (21.7% of total GPU time)
Total overlap time in GPU = 65268.3 micro sec. (3.6% of total GPU time)

Blue = kernel, Green = cudaMemCpyAsync in 2 streams

当我计算上述循环时,我获得的时间为0.000284s,而不使用流的版本为1.703289s(在该版本中,我还计算两个连续的内存副本,内核调用和剩余的memCpy) 。 我想因为我没有使用任何同步结构,可能是我在计算实际完成之前打印时间(我发现很难相信有100%的改进)。

2 个答案:

答案 0 :(得分:2)

我建议进行两项修改:

1)将cuBLAS句柄创建/销毁移动到副本和内核调用之外。它可能通过执行不需要的上下文同步来打破并发。

2)在流中的一个循环中一起完成memcpy。这样,流0的B副本不会进行任何额外的同步,等待A memcpy完成。即这样做:

        for(i = 0; i < nstreams; i++) {
                cudgemm_copyA_in_streams (&plan[i]);
                cudgemm_copyB_in_streams (&plan[i]);
        }

不是这个:

        for(i = 0; i < nstreams; i++)
                cudgemm_copyA_in_streams (&plan[i]);
        for(i = 0; i < nstreams; i++)
                cudgemm_copyB_in_streams (&plan[i]);

如果您无法通过重叠传输和计算获得超过40%的加速,请不要感到惊讶。 Streams在花费相同时间传输和处理数据的工作负载上提供了最大的好处,而且很少有工作负载属于该类别。

答案 1 :(得分:1)

我还建议检查副本的大小,你应该只开始使用不同的流 当传输一块内存的时间可以与计算它所需的时间进行比较时。 如果传输时间与计算时间相比很少,那么添加流会增加管理的开销。 使用Visual Profiler查看各个步骤需要多长时间。制作具有不同内存输入的图形。