コンパイル済み Vulkan シェーダの検査方法:VK_KHR_pipeline_executable_properties による統計と ISA

Post 60。GLSL/SPIR-V を出荷するが、ドライバはそれを実際のマシンコード に再度コンパイルする——レジスタ割り当て、スピル、スケジューリング 込みで。VK_KHR_pipeline_executable_properties はドライバの コンパイラが実際に生成したものを公開する API:ステージ毎の統計 (VGPR、スピル、コードサイズ)と、RADV では完全な ISA ディスアセンブリ。これが RenderDoc のパイプラインインスペクタが 内部で使っているもの。

example.sh
$ ./app
2 executables (compiled shader stages)
  stage { Vertex }:   'vertex'   — Vulkan Vertex Shader
  stage { Fragment }: 'fragment' — Vulkan Fragment Shader
  Driver pipeline hash     16879686041057200563
  SGPRs                    108
  VGPRs                    12
  Spilled SGPRs            0
  Code size                248
  ...
  IR 'NIR Shader(s)'  (The optimized NIR shader(s)):  4982 bytes
  IR 'ACO IR'         (ACO IR after optimizations):    5684 bytes
  IR 'Assembly'       (Final Assembly):              3642 bytes
  ---
  BB0:
      s_wqm_b64 exec, exec          ; befe1d7e
      lds_param_load v2, attr0.x    ; ce0f0002

Example: VulkanExamples/p60_pipelexe

セットアップ

main.cpp (feature + pipeline flag)
vk::PhysicalDevicePipelineExecutablePropertiesFeaturesKHR f{};
f.setPipelineExecutableInfo(true);
device = phys.createDeviceUnique(
    {.pNext = &featureChain, ...},
    extensions = {VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME});

// 重要:このフラグがないとドライバは IR を保持しないかも——
// RADV では、これなしで作成されたパイプラインのデータを要求すると
// 内部表現クエリがクラッシュする(strlen(NULL))。
gi.setFlags(vk::PipelineCreateFlagBits::eCaptureInternalRepresentationsKHR);

3つのクエリ

main.cpp
// 1) パイプラインがどんな実行可能物を生成したか?(ステージ毎に1つ)
auto execs = device->getPipelineExecutablePropertiesKHR(
    {pipeline.get()}, dldi);

// 2) 実行可能物毎の統計
auto stats = device->getPipelineExecutableStatisticsKHR(
    {pipeline.get(), execIndex}, dldi);
// RADV の報告:SGPRs, VGPRs, Spilled SGPRs/VGPRs, Code size,
//               LDS size, Scratch size, instruction mix ...

// 3) 内部表現(ディスアセンブリ!)——手動2パス:
//    Vulkan-Hpp の vector オーバーロードは pData を取得しない。
VkPipelineExecutableInfoKHR ei{VK_STRUCTURE_TYPE_...};
ei.pipeline = pipe; ei.executableIndex = i;
vkGetPipelineExecutableInternalRepresentationsKHR(dev,&ei,&n,nullptr);
std::vector<VkPipelineExecutableInternalRepresentationKHR> reps(n);
for (auto& r : reps) {
    r.sType = VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INTERNAL_REPRESENTATION_KHR;
    blobs[r_i].resize(8<<20);           // 余裕を持ったサイズ
    r.pData    = blobs[r_i].data();
    r.dataSize = blobs[r_i].size();
}
vkGetPipelineExecutableInternalRepresentationsKHR(dev,&ei,&n,reps.data());

注意点

  • pData バッファは事前割り当てが必要——ドライバはあなたが提供 したメモリに書き込み、(RADV では)dataSize を実際のペイロードに 縮小しない。isText IR には strnlen(pData, dataSize) を使う。
  • 統計はドライバ定義の名前——NVIDIA は命令数とレジスタ使用量を 報告し、RADV は ACO パイプライン全体(SGPR/VGPR/スピル+命令ミックス) を報告する。インデックスをハードコードせず、stat.name で照合する。
  • IR の利用可能性は様々:RADV は NIR → ACO IR → 最終アセンブリを 出す;NVIDIA は SASS 的な出力を出す;統計だけしか出さないドライバ もある。
  • これはデバッグ/イントロスペクション API——リリースで無効の ままにして全く問題ない;pipelineExecutableInfo は診断ビルドでのみ 有効化。

なぜ気にするか

レジスタスピルとオキュパンシーは SPIR-V では見えない。スクラッチに 200バイトスピルするシェーダはソースから示唆されるより10倍遅い。 pipeline_executable は「理論上の」最適化がドライバのコンパイラを 実際に通過したか検証する方法——あるいはアンロールしたループが レジスタ予算を吹き飛ばしたと知る方法。


同じカテゴリの類似記事をチェック: C++ Vulkan