5

本質的に、次のような一連の行列 - 行列乗算であるアルゴリズムを実装しています。

Res = M 1 .M 2 .M 3 . ... .M n

私の行列は本当に小さい 100x100 フロートですが、シーケンスは非常に長く、数十億のオーダーです。

行列の乗算に CUBLAS を使用してみましたが、これは遅かったのですが、興味深いことに気付きました。

100x100 を 100x100 マトリックスで乗算するのは遅かったが、1.000.000x100 を 100x100 で乗算するのは比較的高速だった。これは非常に高速であるはずです。これが完了したときに行列を乗算すると、同じ結果が得られますが、より速くなります。

Res1=M1.M2.M3_ _ _ _ _ ... .M n/1000-1 
Res 1 = M 1+n/1000 .M 2+n/1000 .M 3+n/1000 . ... .M 2(n/1000)-1
...
Res 1   = M 1+999*n/1000 .M 2+999*n/1000 .M 3+999*n/1000 . ... .M 1000*(n/1000)-1 
Res = Res 1 *Res 2 * ... *Res 999 

M_1 ... M_n が約 100 の異なる行列のセットに含まれていることは何の価値もないため、スペースの消費は実際には問題ではありません。必要なのは、1 回の操作で複数の乗算を行うことだけです。

ここに私の問題があります。nvidiaがドキュメントで示しているものに触発されたmatrix-matrix(sgemm)実装を行いましたが、cublasの約4倍遅いです。CUBLAS の仕組みを知っている人はいますか? そして、コードがどこかで利用できる場合は?

4

3 に答える 3

2

問題は、cublas などはすべての SM を使用して大きな行列を乗算するように設計されていることです。それはあなたが望むものではありません。小さな行列の乗算をたくさんしたい場合。

これを CUBLAS でうまく処理できるものにキャストする方法があるかもしれませんが、私にはわかりません。私の提案は次のとおりです。

1 つのスレッド ブロックを使用して 2 つの小さな行列を乗算し、結果を出力するカーネルを作成します。

次に、大量のブロックを含むカーネル ログ2 N を起動し、ペアごとに乗算に取り組みます。

  • ステップ 1: M 1 x M 2、M 3 x M 4 ... M N - 2 x M N-1を乗算し、M' 1、M' 2 .. M' N/2を出力します。
  • ステップ 2: M' 1 x M' 2 , M' 3 x M' 4 ... M' N/2 - 2 x M N/2-1を乗算し、M'' 1 ,M'' 2 .. Mを出力します。 '' N/4 ...

50% のメモリ オーバーヘッドが発生しますが、この方法でコアをより有効に活用できると思います。

アップデート

段階的にこれを行いたくない場合は、この方法で行うこともできますが、より多くのコーディングが必要になり、cuBLAS や非同期転送などで得られるものと比較して、おそらくパフォーマンスが低下します。Fermi を使用していて、L1 キャッシュをオフにしたため、SM ごとに 48K の共有メモリがあると仮定しています。

100 個の行列を 2x2 ブロック形式で格納し、各ブロックはメモリ内で連続しています。で始まりmatrix[matrixnum,i,j]ますmatricies[matrixnum*100*100 + i*100*50 + j*50*50]。各ブロックは 50*50*4 バイト ~ 10K であるため、共有メモリには 4 つが快適に収まることに注意してください。

乗算する行列の (Nmatricies/Nblocks) の長さのチェーンを 4 つのスレッドブロックごとに割り当てます。4 つのうちの 1 つが乗算の各ブロックを担当します。

あなたがスレッドブロック 1/4 で、乗算する最初の行列が 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、すべて共有メモリ内)
  • load in myblock[1] =グローバルからの A 1,2
  • myblock[2] にロード =グローバルからB 2,1
  • myblock[0] = myblock[3] + (myblock[1] * myblock[2]) (行列の乗算と加算、すべて共有メモリ内)。

これで、チェーンの一部の行列の残りのシーケンスに対してこれを繰り返すことができ、完了した場合にのみ出力されます。

完了すると、グローバル メモリ内に (#SMs) 行列ができてしまいますが、これはまだ乗算する必要がありますが、グローバル メモリ内に追加の一時ストレージは存在せず、乗算する必要もありません。元のマトリックスと取り組むべきもののリスト以外のデータをグローバルメモリにコピーします。

繰り返しますが、これを行う本当の理由はありませんが、データを段階的に GPU に送信する必要がなく、パフォーマンスがほぼ確実に低下します。グローバル メモリへの書き込みは少なくなりますが、その代償として手巻きの GEMM を使用することになるでしょう。幸いなことに、50 は 8 の倍数ではないため、共有メモリ バンクの競合があまり発生しない可能性があります。

繰り返しになりますが、ボーナス ポイントとして、最初にすべての対行列積のすべてのブロックを事前計算し、次にリストの長さの半分を事前計算できます。

于 2012-02-10T01:44:07.623 に答える