2011-09-05 80 views
2

我通過細分輸入矩陣(A [x/num_of_streams * y] B [x y] = C [x(x))在單GPU上的不同流上運行CUBLAS v2.0(Tesla C2050)/num_of_streams * y]),但不知何故,當我使用CUDA流時,需要更多的時間。以下是代碼片段: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的論壇 -​​

編輯:

我修改我的程序如下:

  //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的時間,VS 1.703289s對於不使用流(在該版本中也有,我的時間兩個連續的內存拷貝,內核調用,其餘的memcpy)的版本。 我想既然我沒有使用任何同步結構,可能是我打印時間計算實際完成之前(我很難相信,有一個100%的改善)。

+1

有在該代碼太多抽象地說,爲什麼什麼,但我會猜* *它是內存拷貝。您的設備具有2個DMA引擎,它可以與最多2個流上的異步存儲器傳輸重疊執行內核,或執行單個雙向直接傳輸。盲目排隊16次轉賬並不是表演的祕訣。你可以發佈你的一種複製方法的代碼嗎? – talonmies

+0

我還沒有走到16條河流,但我正在測試2,4,8條河流。感謝您提醒我關於引擎數量......但第三個副本在內核執行後生效,這是在前兩個副本完成之後,因此當我複製C時DMA引擎應該是空閒的? – Sayan

回答

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爲花費同等時間傳輸和處理數據的工作負載提供了最大的好處,並且很少有工作負載屬於該類別。

+0

我按照你的建議做了,但是當我使用2個數據流時,我得到了一個名義上的好處 - 比如矩陣順序6144的dgemm是1.79s,而沒有使用數據流時的1.92s訂單,差異確實很小,但是有區別)。 – Sayan

+0

請注意,當我將內核調用和memcpytoC分開時,上述註釋是真實的。請查看我編輯的當前情況。 – Sayan

+0

我相信這對2個數據流有好處,但有2個數據流,第一個內核的啓動無法開始處理數據,直到數據已經被複制一半。所以我懷疑使用更多的流將是有益的。 – ArchaeaSoftware

1

我也建議檢查副本的大小,你應該開始使用只 不同的數據流,到時候的一個內存塊傳輸可以比來計算它所需的時間。 如果傳輸時間與計算時間相比很少,那麼添加流會增加管理的開銷。 使用Visual Profiler查看各個步驟需要多長時間。用不同的存儲器輸入製作圖形。

+0

你是對的,我描述了我的程序,這裏是矩陣順序6144 - - 核心時間=總GPU時間的42.75% - 內存複製時間= GPU總時間的28.9% - 總重疊時間在GPU = 65268.3微秒。 (GPU總時間的3.6%) – Sayan