Skip to content
Go back

GPU処理のスカラ化

· Updated:

概要

  • スカラ化scalarizationとは、その処理で使われるレジスタをベクタからスカラになるように変更すること
    • VGPRによる並列処理を、SGPRとマスキングによるループに置き換える
    • ボトルネックになりやすいVGPRの使用率を下げることで、occupancyを向上させようとする狙い
  • スカラ化は、同時に実行されるwaveの数が増えることによる総合的な高速化を目指している
    • SGPRやLDSを使いすぎれば、結局のところoccupancyは改善しない
    • スカラ化したループのイテレーション回数が多すぎれば、結局遅くなってしまうかもしれない

実装例

テクスチャフェッチ その0

  • https://gpuopen.com/learn/porting-detroit-2/から引用
    • テクスチャ配列にアクセスするとき:
      • インデックスがスカラの場合、そのままアクセスできる
      • インデックスがベクタの場合、ReadFirstLaneでスカラ化してアクセスする
        • wave内で異なる値を持つと不具合につながる
        • ddxddyも正確に計算できなくなる
      • インデックスがベクタでNonUniformResourceIndex()を使う場合、その1のようなスカラ化をしてアクセスする
        • wave内で値が異なっていても正常にフェッチできる
        • waterfallと呼んだりする

テクスチャフェッチ その1

  • https://asawicki.info/news_1735_a_better_way_to_scalarize_a_shaderから引用
    • ピクセルシェーダだとhelper laneが悪さをしてクラッシュする
      • ピクセルシェーダではddxddyを計算するためhelper laneが一緒に走る
      • helper laneはactive laneのようにコードを実行するが、inactive laneのようにwave intrinsicsに値を返さないため、下記のコードが無限ループに陥る
    • 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

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

ライトカリング その2

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

用語集

  • 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のこと
    • ifbreakなどで分岐すると、他のActive Laneがブロックを抜けるまでinactive化する
  • Inactive lane
    • 処理を実行しないlaneのこと
    • if文の条件を満たさない場合などで計算する必要がないときに一時的にinactiveになる
    • dispatchされるスレッド数が半端だったときにinactiveなlaneが発生する場合がある
  • Quad
    • 連続するlane4つ分のこと
    • ピクセルシェーダでddxddyを計算するための2x2ブロックをまとめて呼ぶときに使われる
  • Helper lane
    • ピクセルシェーダで勾配を計算するために用意されるlaneのこと

参考文献