【问题标题】:Issues with CUDA streamsCUDA 流的问题
【发布时间】:2011-11-10 08:12:43
【问题描述】:

我通过细分输入矩阵 (A[x/num_of_streams*y]B[xy] = C[ 在单个 GPU (Tesla C2050) 上的不同流上运行 CUBLAS v2.0 x/num_of_streams*y]),但不知何故,当我使用 CUDA 流时,它会花费更多时间。这是代码sn-p:

             //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)

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

【问题讨论】:

  • 代码中有太多抽象,无法说明原因,但我是内存副本。您的设备有 2 个 DMA 引擎,它可以将内核执行与最多 2 个流上的异步内存传输重叠,或者执行单个双向传输。盲目地排队 16 次传输并不是提高性能的秘诀。您可以发布您的一种复制方法的代码吗?
  • 我还没有到 16 个流,但我正在测试 2、4、8 个流。谢谢提醒我引擎的数量...但是第三个副本在内核执行后生效,也就是前两个副本完成之后,所以我复制C时DMA引擎应该是空闲的?

标签: cuda cublas


【解决方案1】:

我建议进行两个更改:

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% 左右的加速,请不要感到惊讶。流为花费相同时间传输和处理数据的工作负载带来最大的好处,而且很少有工作负载属于该类别。

【讨论】:

  • 我按照你的建议做了,但是当我使用 2 个流时我得到了名义上的好处 - 说矩阵订单 6144 的 dgemm 是 1.79s,而没有使用流时是 1.92s(对于更大的订单,差别真的很小,但有差别)。
  • 请注意,当我将内核调用和 memcpytoC 分开时,上述评论是正确的。请查看我的编辑以了解当前发生的情况。
  • 我可以相信 2 个流有好处,但是对于 2 个流,第一次内核启动无法开始处理数据,直到已经复制了一半数据。所以我怀疑使用更多的流会是有益的。
  • 我确实使用了更多的流,但我只使用 2 个流获得了最好的时间......可能就像上面建议的那样,因为有两个 DMA 引擎,所以两个副本可以继续进行串联,无论安排了多少流。正如我在编辑中提到的那样,上面的代码段的时间为 0.000284 秒(我相信我在执行实际完成之前进行计时),但是当我按照你的建议时,我得到了 1.79 秒,这是与分析器反馈相匹配,即 3.6% 的 GPU 时间重叠。我将不得不尝试使用其他内核,也许是转置。
  • 查看我的答案,可能会更清楚。除了在与内核调用不同的循环中对 host->device memcpy 进行软件流水线操作外,您还必须对 device->host memcpy 进行软件流水线化。即添加另一个循环并将设备-> 主机 memcpy 移入其中。
【解决方案2】:

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

【讨论】:

  • 你说得对,我分析了我的程序,这是我得到的矩阵顺序 6144 - - 内核时间 = 总 GPU 时间的 42.75 % - 内存复制时间 = 总 GPU 的 28.9 % time - GPU 中的总重叠时间 = 65268.3 微秒。 (占总 GPU 时间的 3.6%)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-27
  • 2010-12-31
  • 2010-09-22
  • 2011-08-26
  • 2015-02-20
  • 2011-07-11
  • 2013-11-04
相关资源
最近更新 更多