CUDAグローバルメモリトランザクションの「合体」とは何ですか?CUDAガイドを読んでも理解できませんでした。どうやってするの?CUDAプログラミングガイドのマトリックスの例では、行ごとにマトリックスにアクセスすることを「合体」と呼び、列ごとに列にアクセスすることを合体と呼びます。どちらが正しいのか、そしてその理由は?
4 に答える
この情報は、capabality1.xまたはcuda2.0の計算にのみ適用される可能性があります。最近のアーキテクチャとcuda3.0は、より洗練されたグローバルメモリアクセスを備えており、実際、これらのチップの「合体したグローバルロード」はプロファイリングされていません。
また、このロジックを共有メモリに適用して、バンクの競合を回避することもできます。
合体メモリトランザクションは、ハーフワープ内のすべてのスレッドが同時にグローバルメモリにアクセスするトランザクションです。これは単純すぎますが、正しい方法は、連続するスレッドに連続するメモリアドレスにアクセスさせることです。
したがって、スレッド0、1、2、および3がグローバルメモリ0x0、0x4、0x8、および0xcを読み取る場合、それは合体された読み取りである必要があります。
マトリックスの例では、マトリックスをメモリ内に線形に配置する必要があることに注意してください。これは好きなように行うことができ、メモリアクセスはマトリックスのレイアウトを反映している必要があります。したがって、以下の3x4マトリックス
0 1 2 3
4 5 6 7
8 9 a b
このように行ごとに実行できるため、(r、c)はメモリ(r * 4 + c)にマップされます。
0 1 2 3 4 5 6 7 8 9 a b
要素に一度アクセスする必要があり、4つのスレッドがあるとします。どのスレッドがどの要素に使用されますか?おそらくどちらか
thread 0: 0, 1, 2
thread 1: 3, 4, 5
thread 2: 6, 7, 8
thread 3: 9, a, b
また
thread 0: 0, 4, 8
thread 1: 1, 5, 9
thread 2: 2, 6, a
thread 3: 3, 7, b
どちらが良いですか?どちらが合体した読み取りになり、どちらがそうではありませんか?
いずれにせよ、各スレッドは3回のアクセスを行います。最初のアクセスを見て、スレッドがメモリに連続してアクセスするかどうかを確認しましょう。最初のオプションでは、最初のアクセスは0、3、6、9です。連続していない、合体していない。2番目のオプションは0、1、2、3です。連続して!合体!わーい!
おそらく最良の方法は、カーネルを作成し、それをプロファイリングして、非合体のグローバルロードとストアがあるかどうかを確認することです。
メモリ合体は、グローバルメモリ帯域幅の最適な使用を可能にする技術です。つまり、同じ命令を実行する並列スレッドがグローバルメモリ内の連続する場所にアクセスする場合、最も好ましいアクセスパターンが達成されます。
上の図の例は、合体した配置を説明するのに役立ちます。
図(a)では、長さmのn個のベクトルが線形に格納されています。ベクトルjの要素iはvjiで表されます。GPUカーネルの各スレッドは、1つのm長のベクトルに割り当てられます。CUDAのスレッドはブロックの配列にグループ化され、GPUのすべてのスレッドには一意のIDがあります。これは、として定義できます。ここで、はブロックの次元を表し、はブロックインデックスを示し、は各ブロックのスレッドインデックスです。 indx=bd*bx+tx
bd
bx
tx
垂直の矢印は、並列スレッドが各ベクトルの最初のコンポーネント、つまりメモリのアドレス0、m、 2m ...にアクセスする場合を示しています。図(a)に示すように、この場合、メモリアクセスは連続していません。これらのアドレス間のギャップをゼロにすることで(上の図に示す赤い矢印)、メモリアクセスが合体します。
ただし、GPUブロックごとの常駐スレッドの許容サイズはに制限されているため、ここでは問題が少し厄介になりbd
ます。したがって、合体したデータの配置は、最初のベクトルの最初の要素をbd
連続した順序で格納し、続いて2番目のbdベクトルの最初の要素を格納することによって行うことができます。残りのベクトル要素は、図(b)に示すように、同様の方法で格納されます。n (ベクトルの数)がの係数でない場合bd
は、最後のブロックの残りのデータを、0などの簡単な値で埋める必要があります。
図(a)の線形データストレージでは、ベクトルindx
(0≤indx < n )のコンポーネントi (0≤i < m )は;でアドレス指定されます。図(b)の合体したストレージパターンの同じコンポーネントは、次のように扱われます。m × indx +i
(m × bd) ixC + bd × ixB + ixA
、
ここでixC = floor[(m.indx + j )/(m.bd)]= bx
、、ixB = j
およびixA = mod(indx,bd) = tx
。
要約すると、サイズmの多数のベクトルを格納する例では、線形インデックスは次のように合体インデックスにマッピングされます。
m.indx +i −→ m.bd.bx +i .bd +tx
このデータの再配置により、GPUグローバルメモリのメモリ帯域幅が大幅に増加する可能性があります。
出典:「非線形有限要素変形解析における計算のGPUベースの加速」。医用生体工学における数値法のための国際ジャーナル(2013)。
ブロック内のスレッドが連続するグローバルメモリ位置にアクセスしている場合、すべてのアクセスはハードウェアによって単一の要求に結合されます(または合体されます)。行列の例では、行の行列要素が線形に配置され、次の行が続きます。たとえば、ブロック内の2x2マトリックスと2スレッドの場合、メモリ位置は次のように配置されます。
(0,0)(0,1)(1,0)(1,1)
行アクセスでは、thread1は合体できない(0,0)と(1,0)にアクセスします。列アクセスでは、thread1は(0,0)と(0,1)にアクセスしますが、これらは隣接しているため合体できます。
合体の基準は、CUDA3.2プログラミングガイドのセクションG.3.2に適切に文書化されています。短いバージョンは次のとおりです。ワープ内のスレッドは順番にメモリにアクセスしている必要があり、アクセスされているワードは32ビット以上である必要があります。さらに、ワープによってアクセスされるベースアドレスは、32、64、および128ビットアクセス用にそれぞれ64、128、または256バイトにアラインされている必要があります。
Tesla2とFermiのハードウェアは、8ビットと16ビットのアクセスを合体させるのに問題はありませんが、ピーク帯域幅が必要な場合は避けるのが最善です。
Tesla2およびFermiハードウェアの改善にもかかわらず、合体は決して時代遅れではないことに注意してください。Tesla2またはFermiクラスのハードウェアでも、グローバルメモリトランザクションを統合できないと、パフォーマンスが2倍に低下する可能性があります。(Fermiクラスのハードウェアでは、これはECCが有効になっている場合にのみ当てはまるようです。連続しているが合体していないメモリトランザクションは、Fermiで約20%のヒットを被ります。)