簡體   English   中英

在一次操作中進行多個矩陣 - 矩陣乘法

[英]Doing multiple matrix-matrix multiplications in one operation

我正在實現一種算法,實質上是一系列矩陣 - 矩陣乘法,如下所示:

Res = M1.M2.M3. ... .Mn

我的矩陣實際上是小的100x100浮點數,但序列非常長,大約數十億。

我嘗試使用CUBLAS來進行矩陣乘法,但這很慢,但我確實注意到了一些有趣的東西。

將100x100與100x100矩陣相乘的速度很慢,但是將1.000.000x100乘以100x100相對較快,這讓我想到了。如果我從左到右進行掃描而不是並行掃描10.000次。 這應該是非常快的,如果我在完成這項工作后將我的矩陣相乘,我會得到相同的結果 - 只是更快。

Res1 = M1.M2.M3. ... .Mn/1000-1
Res1 = M1+n/1000.M2+n/1000.M3+n/1000. ... .M2(n/1000)-1
...
Res1  = M1+999*n/1000.M2+999*n/1000.M3+999*n/1000. ... .M1000*(n/1000)-1
Res = Res1*Res2* ... *Res999

毫無價值M_1 ... M_n在一組約100個不同的矩陣中,因此空間消耗並不是真正的問題,我需要做的就是在一次操作中進行多次乘法。

現在這是我的問題。 我已經完成了一個矩陣矩陣(sgemm)實現的靈感來自他們的文檔中的一個nvidia演示,但它的順序大約是cublas的4倍。 有誰知道CUBLAS如何運作? 如果代碼在某處可用?

你看過最新的CUBLAS(4.1版)嗎? 它包括一個新的批量GEMM模式,專門用於大批量的小矩陣矩陣乘法。 我建議做一個成對的乘法樹,正如Jonathan Dursi在他的回答中所建議的那樣,使用CUBLAS批處理的API加速它,而不是像他建議的那樣編寫你自己的自定義內核。

CUBLAS 4.1包含在CUDA Toolkit v4.1中

CUBLAS BATCHED GEMM API提高了小矩陣批次的性能

問題是cublas等設計用於使用所有SM來乘以大矩陣。 那不是你想要的; 你想做很多小矩陣乘法。

可能有某種方法可以將其轉化為CUBLAS可以為您做好的事情,但我沒有看到它。 我的建議如下:

編寫一個使用一個線程塊的內核來乘以兩個小矩陣,然后輸出結果。

然后使用數量的塊啟動內核log 2 N並成對處理乘法:

  • 步驟1:乘M 1 X M 2,M 3×M4 ...,M N - 2×M N-1,輸出M '1,M' 2 .. M” N / 2
  • 步驟2:乘以M '1×M' 2,M '3×M' 4 ... M” N / 2 - 2×MN / 2-1,輸出M'1,M'2 ..中號'' N / 4 ......

等等

存在50%的內存開銷因素,但我認為您可以通過這種方式更好地利用內核。

更新

好吧,如果你真的不想分階段這樣做,你可以這樣做,但它需要更多的編碼,與你使用cuBLAS和異步傳輸的東西相比,性能可能會更差。 我假設您正在使用Fermi,並且您已關閉L1緩存,因此每個SM有48K共享內存。

以100x2格式存儲100個矩陣,每個塊在內存中連續。 因此matrix[matrixnum,i,j]matricies[matrixnum*100*100 + i*100*50 + j*50*50]開始matricies[matrixnum*100*100 + i*100*50 + j*50*50] 請注意,每個塊是50 * 50 * 4字節~10K,因此4可以很好地適合共享內存。

為每個4個線程塊分配一個(Nmatricies / Nblocks)長矩陣的矩陣乘以,其中四個中的一個負責乘法的每個塊。

假設你是第4個中的第1個線程,你要成倍增加的第一個基礎是AxB。 你負責(1,1)的結果 - (AB) 1,1 = A 1,1 B 1,1 + A 1,2 * B 2,1 您將A 1,1預加載到共享內存中的myblock [0]中。

  • 在全局內存中加載myblock [1] = B 1,1
  • myblock [3] = myblock [0] * myblock [1](矩陣mult,全部在共享內存中)
  • 在myblock中加載[1] =來自全局的1,2
  • 加載myblock [2] = B 2,1來自全球
  • myblock [0] = myblock [3] +(myblock [1] * myblock [2])(矩陣mult和加法,全部在共享內存中)。

現在,您可以對鏈中部分矩陣序列的其余部分重復此操作,僅在完成時輸出。

當你完成后,你最終會得到(#SMs)全局內存中的matricies,它仍然必須成倍增加,但是在全局內存中不會有任何額外的臨時存儲空間,你將不必將數據復制到除原始基質之外的全局存儲器以及要處理的那些列表中。

再說一遍,除了你不能分階段將數據傳送到GPU之外,沒有真正的理由這樣做,而且性能幾乎肯定會更差; 全局內存寫入次數較少,但您可能會使用手動GEMM來支付費用。 好消息是50不是8的倍數,所以你可能不會有太多的共享內存庫沖突。

同樣,對於獎勵積分,您可以首先預先計算所有成對矩陣產品的所有塊,然后再列出列表長度的一半。

LIBXSMM - 針對小型,密集或稀疏矩陣乘法的英特爾架構的庫,以及小卷積,旨在為小型矩陣乘法利用最佳性能。

與NVidia CUBLAS(或英特爾MKL)相比,LIBXSMM不依賴於批處理界面。 相反,可以安排單獨的呼叫並且還提供“下一個位置”,即,下一個乘法的操作數/矩陣所在的位置(在存儲器中)。 優點是不需要描述批處理的顯式數據結構或索引格式。

#include <libxsmm.h>

int main()
{
  const libxsmm_gemm_prefetch_type prefetch = LIBXSMM_PREFETCH_AUTO;
  const double alpha = 1.0, beta = 1.0; /* accumulate C-matrix */
  const int m = 23, n = 23, k = 23;     /* some problem size */
  libxsmm_dmmfunction xmm = NULL;       /* function pointer */

  xmm = libxsmm_dmmdispatch(23, 23, 23, /* some problem size */
          NULL/*lda*/, NULL/*ldb*/, NULL/*ldc*/,
          &alpha, &beta, NULL/*flags*/,
          NULL/*&prefetch*/);

  if (xmm) { /* JiT'ted code has been generated */
#   pragma omp parallel for
    for (int i = 0; i < nbatch; ++i) {
      const double *const ai = a + i * asize;
      const double *const bi = b + i * bsize;
      /* e.g., matrix C is accumulated (instead of streamed) */
      double *const ci = c /*+ i * csize*/;
      /* optionally provide "next locations" */
      xmm(ai, bi, ci/*,
          ai + 1 * asize,
          bi + 1 * bsize,
          ci + 0 * csize
      */);
    }
  }
}

LIBXSMM生成高度優化和專用的代碼(JiT),它利用最新的指令集擴展(SSE3,AVX,AVX2和AVX-512)。 LIBXSMM是可用的下一個非允許許可證(BSD-3子句)。

注意:這不是CUBLAS(最初要求的)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM