概要
- スカラ化とは、その処理で使われるレジスタをベクタからスカラになるように変更すること
- VGPRによる並列処理を、SGPRとマスキングによるループに置き換える
- ボトルネックになりやすいVGPRの使用率を下げることで、occupancyを向上させようとする狙い
- スカラ化は、同時に実行されるwaveの数が増えることによる総合的な高速化を目指している
- SGPRやLDSを使いすぎれば、結局のところoccupancyは改善しない
- スカラ化したループのイテレーション回数が多すぎれば、結局遅くなってしまうかもしれない
実装例
テクスチャフェッチ その0
- https://gpuopen.com/learn/porting-detroit-2/から引用
- テクスチャ配列にアクセスするとき:
- インデックスがスカラの場合、そのままアクセスできる
- インデックスがベクタの場合、
ReadFirstLaneでスカラ化してアクセスする- wave内で異なる値を持つと不具合につながる
ddxやddyも正確に計算できなくなる
- インデックスがベクタで
NonUniformResourceIndex()を使う場合、その1のようなスカラ化をしてアクセスする- wave内で値が異なっていても正常にフェッチできる
- waterfallと呼んだりする
- テクスチャ配列にアクセスするとき:
テクスチャフェッチ その1
- https://asawicki.info/news_1735_a_better_way_to_scalarize_a_shaderから引用
- ピクセルシェーダだとhelper laneが悪さをしてクラッシュする
- ピクセルシェーダでは
ddxとddyを計算するためhelper laneが一緒に走る - helper laneはactive laneのようにコードを実行するが、inactive laneのようにwave intrinsicsに値を返さないため、下記のコードが無限ループに陥る
- ピクセルシェーダでは
- helper laneのないコンピュートシェーダでは問題なく動作する
- ピクセルシェーダだとhelper laneが悪さをしてクラッシュする
float4 color = float4(0.0, 0.0, 0.0, 0.0);
while(true)
{
uint scalarTextureIndex = WaveReadLaneFirst(textureIndex);
[branch]
if(scalarTextureIndex == textureIndex)
{
color = MyDynamicTextureIndexing(textureIndex);
break;
}
}WaveReadLaneFirst()でスカラ値に変換して、coherentなテクスチャフェッチを行うbreakしたスレッドはすべてのスレッドがループを抜けるまでinactive化するので、WaveReadLaneFirst()は、まだ処理を終えていないスレッドのインデックスを返す
テクスチャフェッチ その2
- https://asawicki.info/news_1735_a_better_way_to_scalarize_a_shaderより引用
- はじめにhelper laneをinactiveにすることで、ピクセルシェーダでも正常に動作する
float4 color = float4(0.0, 0.0, 0.0, 0.0);
uint currThreadIndex = WaveGetLaneIndex();
uint2 currThreadMask = uint2(
currThreadIndex < 32 ? 1u << currThreadIndex : 0,
currThreadIndex < 32 ? 0 : 1u << (currThreadIndex - 32));
uint2 activeThreadsMask = WaveActiveBallot(true).xy;
[branch]
if(any((currThreadMask & activeThreadsMask) != 0))
{
while(true)
{
uint scalarTextureIndex = WaveReadLaneFirst(textureIndex);
[branch]
if(scalarTextureIndex == textureIndex)
{
color = MyDynamicTextureIndexing(textureIndex);
break;
}
}
}WaveActiveBallot(true)でactive laneのみを列挙したビットマスクを用意するactiveThreadsMaskをif文の条件に使うことでhelper laneをinactive化する- その1のときと同じく
WaveReadLaneFirst()によって各フェッチをスカラ化する
ライトカリング その1
- https://flashypixels.wordpress.com/2018/11/10/intro-to-gpu-scalarization-part-2-scalarize-all-the-lights/より引用
- STRATEGY #1は https://asawicki.info/news_1735_a_better_way_to_scalarize_a_shaderのTRADITIONAL相当
- テクスチャインデックスのかわりにセルのインデックスを用いる
- STRATEGY #1は https://asawicki.info/news_1735_a_better_way_to_scalarize_a_shaderのTRADITIONAL相当
ライトカリング その2
- https://flashypixels.wordpress.com/2018/11/10/intro-to-gpu-scalarization-part-2-scalarize-all-the-lights/より引用
- ライトリストのオフセットを並列にインクリメントする
- ライトのインデックスをスカラ化して、対応するlaneのみで処理を行う
- ソートされたライトリストから、処理されていない最小値を
WaveActiveMinで取得する - 結果として、平坦化したユニークなライトリストと同等になる
uint v_lightOffset = 0;
while(v_lightOffset < v_lightCount) {
uint v_lightIdx = GetLightIdx(v_lightStart, v_lightOffset);
uint s_lightIdx = WaveActiveMin(v_lightIdx);
if (s_lightIdx == v_lightIdx) {
v_lightOffset++;
LightData s_light = Lights[s_lightIdx];
ProcessLight(s_light);
}
}ライトカリング その3
- https://gpuopen.com/learn/porting-detroit-3/より引用
- その2の亜種
- イテレーションを昇順から降順に変更したもの
- インクリメントをデクリメントに
- MinをMaxに
用語集
- Occupancy
- GPUの活用度のような指標
- 本来の力が発揮できていれば高く、そうでなければ低くなる
- Radeonでは、実際に動作しているwavefrontの割合を指す
- Divergence
- waveにおいてスレッドの一部が異なる処理を実行すること
- GPUの実行モデルはSIMTであるため、スレッドごとに異なる処理を実行するには、スレッドをマスキングしながらすべての処理を実行する必要があるので、ボトルネックになりやすい
- Divergent / Coherent
- divergentは一部のスレッドが異なる処理を実行している状態
- coherentはすべてのスレッドが同じ処理を実行している状態
Radeon
- VGPR (Vector General-Purpose Register)
- スレッドごとに異なる値を格納するためのレジスタファイル
- GCNでは、SIMDあたり64KiB
- SGPR (Scalar General-Purpose Register)
- スレッド間で同じになる値を格納するためのレジスタファイル
- GCNでは、CUあたり12.8KiB
- CU (Compute Unit)
- 処理系の単位
- SIMD
- 32/64個のスレッドを処理できる並列実行器
- GCNでは、CUあたり4つ
- LDS (Local Data Store)
- グループ共有メモリとして使われる
- CUあたり64KiB
- スレッド (Thread)
- 実行単位
HLSL
- Wave
- スレッドを束ねたもの
- GeForceではwarp、Radeonではwavefrontと呼ばれる
- GeForceやRDNAでは32スレッドを、GCNでは64スレッドを束ねる
- Lane
- Waveを構成するスレッドのこと
- Active lane
- 処理を実行するlaneのこと
ifやbreakなどで分岐すると、他のActive Laneがブロックを抜けるまでinactive化する
- Inactive lane
- 処理を実行しないlaneのこと
if文の条件を満たさない場合などで計算する必要がないときに一時的にinactiveになる- dispatchされるスレッド数が半端だったときにinactiveなlaneが発生する場合がある
- Quad
- 連続するlane4つ分のこと
- ピクセルシェーダで
ddxとddyを計算するための2x2ブロックをまとめて呼ぶときに使われる
- Helper lane
- ピクセルシェーダで勾配を計算するために用意されるlaneのこと
参考文献
- https://asawicki.info/news_1735_a_better_way_to_scalarize_a_shader
- https://advances.realtimerendering.com/s2017/2017_Sig_Improved_Culling_final.pdf
- https://flashypixels.wordpress.com/2018/11/10/intro-to-gpu-scalarization-part-1/
- https://flashypixels.wordpress.com/2018/11/10/intro-to-gpu-scalarization-part-2-scalarize-all-the-lights/
- https://gpuopen.com/learn/optimizing-gpu-occupancy-resource-usage-large-thread-groups/
- https://advances.realtimerendering.com/s2016/Siggraph2016_idTech6.pdf
- https://shikihuiku.github.io/post/wave_intrinsics1/
- https://gpuopen.com/learn/porting-detroit-2/
- https://gpuopen.com/learn/porting-detroit-3/