syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

VulkanシェーダーでSub-group命令を使う

NVIDIAはKepler (Compute Capability 3.0) 世代のハードウェアにおいて、Warp Shuffle命令を実装しました。WarpシャッフルはCUDAから組み込み関数 (intrinsic function) の形で利用できるSIMD命令の一種で、Warpと呼ばれるスレッドグループ内での並列データ交換を実現する機能です。NVIDIAGPUでは、ひとつのWarpは32個のハードウェアスレッドからなり、またWarp内の各スレッドはすべて同じ命令を実行します。つまり、ひとつのWarp内のスレッド群はすべて同期して並列動作する*1ため、Warpシャッフルを使うことで、データ列の総和を求めるリダクション演算などを、共有メモリを使うよりも高速に並列実行することができます。

Warpシャッフルに関しては、以前「CUDA Warpシャッフル命令のエミュレーション」という記事を書きましたが、CUDAの共有メモリに関する知識があれば、Warpシャッフルがいかに便利で簡潔な機能であるかを理解できると思います。

WarpシャッフルのようなSIMD命令は、汎用的な計算(GPGPU)だけでなく、グラフィックスのポストエフェクト処理などの高速化にも非常に有用なのですが、APIの停滞のせいであまり標準化が進んでいませんでした。NVIDIAWarpシャッフル命令をHLSL/GLSLから利用できるベンダー拡張を提供していますが、当然NVIDIA環境でしか使えないという制約があります。

Warpシャッフル相当の機能はまた、OpenCL 2.0の拡張機能cl_khr_subgroupsおよびOpenCL 2.1のコア機能として策定されていますが、2018年3月現在、OpenCLの実装はどのベンダーも停滞しており、OpenCLには期待できない状況です。一方、OpenGLにもARB拡張GL_ARB_shader_ballotとして存在しており、OpenCL同様にサブグループ (Sub-groupあるいはSubgroup) と呼ばれています。GL_ARB_shader_ballotはCUDAのWarpシャッフルのサブセットであるものの、GLSLを使えばOpenGLだけでなくVulkanからも基本的なサブグループの機能を利用できます。

Vulkanの学習を兼ねて、コンピュートシェーダーにてサブグループ命令を利用するサンプルコードを書いてみました。実行にはVulkan 1.0とVK_EXT_shader_subgroup_ballot拡張をサポートする環境が必要となります。NVIDIAハードウェアの場合、Kepler世代以降であれば拡張をサポートしているはずです。サンプルはWindows環境向けですが、ウィンドウシステムへのアクセスを必要としないオフスクリーンのコンピュート機能だけを使っているため、他の環境に移植するのは比較的簡単だと思います*2

以前書いた記事「Vulkan SDK付属のGLSLコンパイラー」では、Vulkan SDK付属のシェーダーコンパイラーがあまりに未完成なことを嘆いていましたが、その後着々と進化を続け、ほぼ使えるレベルに達したようです。
なお、今回はホストコードの記述量を減らすため、C++向けのラッパー (vulkan.hpp) を使っています。もともとこのラッパーはNVIDIAから寄贈されたものをベースに公式化されたらしいのですが、設計不足な点が散見されたり、一部の関数テンプレートのコンパイルが通らないというひどいバグが混入していたりと、まるでテストされていないことがありありとうかがえます。はっきり言ってプロダクションコードに使うには厳しい印象です。本気でVulkanの導入を検討する場合は、ぶっちゃけこのラッパーは使わないほうがいいでしょう。

なお、AMDAnvil*3という上位レベルのVulkanベースフレームワークを公開していますが、いつも仕事が中途半端なAMDが、この先ちゃんと継続的にメンテしてくれるのかどうか不安です。AMD APP SDKも、2015年8月にリリースされたv3.0で放置されたままという体たらくなので、正直期待できません。

Vulkan 1.1とSub-group命令

先日Vulkanのアップデートとしてv1.1が発表されましたが、Direct3D 12が先行していた、マルチGPUの活用やシェーダーモデル (SM) 6.0に対応する形で、重要な新機能が追加されているようです。サブグループ演算 (subgroup operation) に関しても、既存のOpenGL互換のARB拡張とは別のKHR拡張 (GL_KHR_shader_subgroup) およびホストAPIの正式仕様が策定され、CUDAと同等あるいはそれ以上の機能を獲得したようです。

なお、DirectXにおけるSM 6.0の目玉とも言えるのが、Sub-groupに相当するWave命令セットです。Waveは機能レベル12_0以上でないと使えないとか、そもそもDirect3D 12自体がWindows 10でないと使えないとかいう制約があり、個人的にはプラットフォーム制約の少ないVulkanのほうが有望だと思うのですが、いずれにせよこれでようやくCUDA以外でも高速なSIMD命令が標準的に利用できる土台が整い始めました。
とはいえ、HLSLもGLSLも、CUDA C++に比べるとプログラマビリティは遥かに劣ります。個人的にはC++のテンプレートが使えないのが一番痛いです。純粋なGPGPU用途であれば、結局CUDAの地位は安泰であり、ただ単にNVIDIAハードウェアの能力を最大限活用できるAPIというだけでなく、地球上でもっとも生産性の高いGPGPU開発基盤であることは疑いの余地がありません。VulkanはせっかくSPIR-Vという強力なシェーダー中間言語を持って生まれたのだから、今後はシェーダーコンパイラーのさらなる飛躍と、Metalシェーディング言語のような新しいC++ベースのフロントエンドのサポートに期待したいところです。

*1:GPUコアの1サイクルでWarp内のすべてのスレッドが同時に駆動するとは限りませんが、論理的には必ず同期するようになっています。

*2:OpenGLでは仮にコンピュートを利用するだけであっても、GLコンテキストの作成にウィンドウハンドルを必要とし、しかも実行デバイスを明示的に選択する機能すら標準で提供されていませんでしたが、Vulkanは遥かに洗練されており、OpenCLやDirectComputeと似たような使い方ができるようになっています。

*3:Anvilとは「金床」(かなとこ)という意味ですが、UbisoftのAnvil Engineとカブっているので、できれば別の名称にして欲しかったです。