プログラミング
CUDA共有メモリのスウィズリング
CUDA Shared Memory Swizzling (leimao.github.io)
要約
この記事は、CUDAにおける共有メモリのバンクコンフリクトとその解決策について解説しています。パディングによるメモリの無駄遣いを避けつつ、共有メモリへのアクセスパターンを再配置する「スウィズリング」という手法を紹介し、その数式とプロパティについて詳細に説明しています。行列転置を例に、スウィズリングがどのようにバンクコンフリクトを解消するかを示しています。
全文翻訳
CUDA共有メモリのスウィズリング
05-14-2024 07-31-2024 blog 26分読書(約3899語)訪問者数
はじめに
CUDAカーネルで共有メモリを使用する際、共有メモリのバンクコンフリクトに注意する必要があります。深刻な共有メモリのバンクコンフリクトは、パフォーマンスに大きなペナルティをもたらす可能性があります。共有メモリのバンクコンフリクトに対処する簡単な方法の1つは、パディングを使用することです。しかし、パディングは共有メモリを無駄にし、他の欠点も持つ可能性があります。このブログ記事では、スウィズリングを使用して共有メモリのバンクコンフリクトに対処する方法について説明したいと思います。スウィズリングは、共有メモリを無駄にすることなく共有メモリのバンクコンフリクトを回避するために使用できる、より複雑なテクニックです。
CUDA共有メモリのスウィズリング
スウィズリングの例
パディングを使用せずに共有メモリでデータをキャッシュする場合、ワープによる共有メモリへの読み込みまたは書き込みが共有メモリのバンクコンフリクトを引き起こすことは非常に一般的です。スウィズリングは、共有メモリのバンクコンフリクトを回避するために、共有メモリインデックスのマッピングを再配置するテクニックです。行列転置は、実装がパディングまたはスウィズリングを使用しない場合に共有メモリのバンクコンフリクトを引き起こす可能性のある完璧な例です。
上記の例では、共有メモリはfloat型の2次元配列で、サイズは32 × 16です。行列転置の観点から見ると、各ワープはグローバルメモリから32個の値の行を読み込み、スウィズリングを使用して共有メモリに書き込みます。共有メモリへの書き込み時に共有メモリのバンクコンフリクトは発生しません。行列転置を実行するために、各ワープは共有メモリから32個の値のスウィズルド「列」を2つ読み込み、それらをグローバルメモリに書き込みます。たとえば、スウィズルド列0と1は、それぞれ黄色とシアンで色付けされています。このように、共有メモリからの読み込み時に共有メモリのバンクコンフリクトは1つしか発生しません。スウィズリングを使用しない場合、共有メモリからの読み込み時に16個(2ウェイ)の共有メモリのバンクコンフリクトが発生します。もちろん、共有メモリがfloat型の2次元配列でサイズが32 × 32であれば、共有メモリへの書き込み時および共有メモリからの読み込み時に共有メモリのバンクコンフリクトは発生しません。
スウィズリングの公式
共有メモリ上のT array[][NX]という配列を考えます。ここで、NX × sizeof(T) == SWIZZLE_SIZEと定義します。SWIZZLE_SIZEとして許容される値は、32以上で2のべき乗(32、64、128、256など)です。T array[][NX]内のインデックス[y][x]が与えられた場合、スウィズルドインデックスx_swzは次のように計算できます。
SWIZZLE_SIZEバイトセグメント内のTCバイトチャンクのインデックスを計算します。
i_chunk = (y × NX + x) × sizeof(T) / sizeof(TC)
y_chunk = i / (SWIZZLE_SIZE / sizeof(TC))
x_chunk = i % (SWIZZLE_SIZE / sizeof(TC))
XOR演算を使用してTCバイトチャンクのスウィズルドインデックスを計算します。
x_chunk_swz = y_chunk ^ x_chunk
スウィズルドインデックスを計算します。
x_swz = x_chunk_swz × sizeof(TC) / sizeof(T) % NX + x % (sizeof(TC) / sizeof(T))
スウィズリングのプロパティ
このスウィズリングの公式は、以下のプロパティを持っています。
1. スウィズリングの前後のインデックスは1対1でマッピングされる必要があります。
2. NXは2のべき乗である必要があります。
3. 任意のxと任意の{y, y+1, y+2, ..., y+31}に対して、一意のスウィズルドインデックスx_swzの数を最大化する必要があります。
プロパティ1は、スウィズリング中にデータ損失がないことを保証します。プロパティ2は、スウィズリングの前後のインデックスが1対1でマッピングされることを保証します。ここでは、スウィズリングの公式からこれらのプロパティの非公式な数学的証明をいくつか示します。
証明
まず、プロパティ1を証明します。
x_chunk = i_chunk % (SWIZZLE_SIZE / sizeof(TC))
= ((y × NX + x) × sizeof(T) / sizeof(TC)) % (NX × sizeof(T) / sizeof(TC))
= (y × NX × sizeof(T) / sizeof(TC) + x × sizeof(T) / sizeof(TC)) % (NX × sizeof(T) / sizeof(TC))
= (y × NX × sizeof(T) / sizeof(TC) % (NX × sizeof(T) / sizeof(TC)) + x × sizeof(T) / sizeof(TC) % (NX × sizeof(T) / sizeof(TC))) % (NX × sizeof(T) / sizeof(TC))
= (x × sizeof(T) / sizeof(TC) % (NX × sizeof(T) / sizeof(TC))) % (NX × sizeof(T) / sizeof(TC))
= x × sizeof(T) / sizeof(TC) % (NX × sizeof(T) / sizeof(TC))
= (x % NX) × sizeof(T) / sizeof(TC)
= x × sizeof(T) / sizeof(TC)
ここで、x_chunkの別の同等な公式を導出したようです。sizeof(T) / sizeof(TC)は、sizeof(TC) / sizeof(T)が2のべき乗である場合、ビット(右)シフト操作です。
y_chunk = i_chunk / (SWIZZLE_SIZE / sizeof(TC))
= ((y × NX + x) × sizeof(T) / sizeof(TC)) / (NX × sizeof(T) / sizeof(TC))
= (y × NX × sizeof(T) / sizeof(TC) + x × sizeof(T) / sizeof(TC)) / (NX × sizeof(T) / sizeof(TC))
= y × NX × sizeof(T) / sizeof(TC) / (NX × sizeof(T) / sizeof(TC)) + x × sizeof(T) / sizeof(TC) / (NX × sizeof(T) / sizeof(TC))
= y + x / NX
= y
ここで、y_chunkの別の同等な公式も導出したようです。
x_chunk_swz = y_chunk ⊕ x_chunk
= y ⊕ (x × sizeof(T) / sizeof(TC))
= y / (NX × sizeof(T) / sizeof(TC)) × NX × sizeof(T) / sizeof(TC) + (y % (NX × sizeof(T) / sizeof(TC))) ⊕ (x × sizeof(T) / sizeof(TC))
= y / (NX × sizeof(T) / sizeof(TC)) × NX × sizeof(T) / sizeof(TC) + ((y % NX) × sizeof(T) / sizeof(TC)) ⊕ (x × sizeof(T) / sizeof(TC))
= y / (NX × sizeof(T) / sizeof(TC)) × NX × sizeof(T) / sizeof(TC) + (y % NX) ⊕ x × sizeof(T) / sizeof(TC)
ここで、⊕はビットごとのXOR演算です。y_chunkまたはx_chunkのいずれかが定数である場合、マッピングは1対1のマッピングです。
x_swz = x_chunk_swz × sizeof(TC) / sizeof(T) % NX + x % (sizeof(TC) / sizeof(T))
ここでは、証明が少し非公式になります。sizeof(TC) / sizeof(T)の連続したx値は、1つのユニークなトランクインデックスx_chunkにマッピングされるため、x_chunkとx_chunk_swzのマッピングは1対1のマッピングであり、1つのx_chunk_swz値は1つのユニークなx_chunk_swz × sizeof(TC) / sizeof(T) % NX値にマッピングされます。スウィズルドインデックスx_swzと元のインデックスxの間に1対1のマッピングを作成するために、オフセットx % (sizeof(TC) / sizeof(T))が追加されます。したがって、スウィズリングの前後のインデックスは1対1でなければなりません。