複数GPU環境でGPUを指定してUE製アプリを動かしたときの話

## はじめに
この記事では、複数GPU環境でGPUを指定してUE製アプリを動かした時の困ったことを共有します。
自分と同じような初心者の方がやらざるを得なくなり、困ったときにちょっとでも参考になればと思います。
...ほぼはじめての記事ですのでお手柔らかにお願いします。

## 対象読者
複数GPUを積んだ環境で、指定したGPUを使用してUE製のアプリを起動させたい方。
指定して起動させる方法はわかったけどもなぜかうまく起動できなかった方。

## 実行環境
- OS: Linux（Debian11）
- Unreal Engine: 5.3.2、5.2.1
- GPU: NVIDIA T4、NVIDIA L4
- 実行サーバー: Google Cloud（Compute Engine）

## やりたかったこと
クラウド上のサーバーに複数のGPUを配置し、Pixel Streamingを利用したUE製アプリケーションを同時に複数起動させる試みをしました。
（後述しますが、実はこの方法だと余計にお金がかかってしまったり、望んでいたパフォーマンスを出せなかったことから断念しています...）

当初、GPUの使用率が上がるにつれて勝手に使用するGPUを分散してくれるのかなと思っていました。

...が、もちろんそんなことはなく、1台目のGPUの負荷が最高に高まっていても一向に2台目のGPUを使用してくれませんでした。

そこで、何らかの追加実装か実行時の引数が必要なのかなと思い、今回調べることとなりました。

## 結論、どうすればよいのか？
[こちらのフォーラム](https://forums.unrealengine.com/t/explicitly-choosing-which-gpu-to-use/436698/8?u=ggmaichale)にあるように、実行時に```-graphicsadapter```を引数として渡し、GPUを指定すれば良いみたいです。
```
${fileName} -graphicsadapter=${GPUのindex番号(※)} -RenderOffscreen -ResX=854 -ResY=480 -ForceRes
```
上記のような形で実行することで指定できます。
（※）正確には、0から始まるハードウェアマシン上の番号です。そのため、1台目なら0、2台目なら1になるのかなと思っています。こちらは、nvidia-smiやnvidia-smiの--query-gpuオプションで確認可能です。そのため、どのGPUを使用したいか確認する場合には、こちらから調べて指定すると良いと思います。

<br />
<br />
<br />

...本来は上記で良いのですが、**「自分の環境」** では

```
# 2つ目のGPUを動作させる場合
${fileName} -graphicsadapter=2 -RenderOffscreen -ResX=854 -ResY=480 -ForceRes
```
というように、GPUのindex番号ではなく、index番号に+1する必要がありました（原因は後述します）。

## 実際にやってみる
謎の挙動の原因を探る前に実際に動くかどうかを確かめてみましょう。

今回どのような形で確かめているかと言いますと
1. Linuxのサーバーのため、Linux用のクロスコンパイルを行い、Linux用のpackageファイルを作成する
2. 何らかの方法でサーバーへファイルをデプロイ
3. 前述の起動引数を指定して起動
4. エラー発生の有無の確認およびnvidia-smiの実行

という形で確かめております。
```
# 1つ目のGPUを使用した場合
Wed Dec 20 16:40:33 2023
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 525.105.17   Driver Version: 525.105.17   CUDA Version: 12.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA L4           On   | 00000000:00:03.0 Off |                    0 |
| N/A   51C    P0    66W /  72W |   2153MiB / 23034MiB |     98%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  NVIDIA L4           On   | 00000000:00:04.0 Off |                    0 |
| N/A   39C    P8    17W /  72W |      2MiB / 23034MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A     10461    C+G   **************                   2148MiB |
+-----------------------------------------------------------------------------
```

```
# 2つ目のGPUを使用した場合
Wed Dec 20 16:41:30 2023
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 525.105.17   Driver Version: 525.105.17   CUDA Version: 12.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA L4           On   | 00000000:00:03.0 Off |                    0 |
| N/A   58C    P8    31W /  72W |      6MiB / 23034MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  NVIDIA L4           On   | 00000000:00:04.0 Off |                    0 |
| N/A   45C    P0    63W /  72W |   2153MiB / 23034MiB |     98%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A     10703      G   ************                        3MiB |
|    1   N/A  N/A     10703    C+G   ************                     2148MiB |
+-----------------------------------------------------------------------------+
```

どうやら、上記のログのようにうまく動いているようですね。

## 仮にgraphicsadapterに1を指定するとどうなるか
ということで、動かせることはわかったため、graphicsadapterに1を渡してみるとどうなるか見てみましょう。

```
// UE5.2.1の場合
$ ./MultiGPUTest_5_2_1.sh -graphicsadapter=1 -RenderOffscreen -ResX=854 -ResY=480 -ForceRes
5.2.1-0+UE5 1009 0
Disabling core dumps.
sh: 1: xdg-user-dir: not found
WARNING: lavapipe is not a conformant vulkan implementation, testing use only.
Failed to find symbol file, expected location:
"/***/***/***/***/Linux/MultiGPUTest_5_2_1/Binaries/Linux/MultiGPUTest_5_2_1-Linux-Shipping.sym"
LowLevelFatalError [File:.\Runtime/VulkanRHI/Private/VulkanUtil.cpp] [Line: 1017]
VulkanRHI::vkCreateQueryPool(Device->GetInstanceHandle(), &PoolCreateInfo, VULKAN_CPU_ALLOCATOR, &QueryPool) failed, VkResult=-8
 at .\Runtime/VulkanRHI/Private/VulkanQuery.cpp:52
 with error VK_ERROR_FEATURE_NOT_PRESENT
Signal 11 caught.
Malloc Size=262146 LargeMemoryPoolOffset=262162
CommonUnixCrashHandler: Signal=11
Engine crash handling finished; re-raising signal 11 for the default handler. Good bye.
Segmentation fault

// UE5.3.2の場合
$ ./MultiGPUTest_5_3_2.sh -graphicsadapter=1 -RenderOffscreen -ResX=854 -ResY=480 -ForceRes
5.3.2-0+UE5 1009 0
Disabling core dumps.
sh: 1: xdg-user-dir: not found
WARNING: lavapipe is not a conformant vulkan implementation, testing use only.
Unsupported intrinsic: vec1 32 ssa_173 = intrinsic load_subgroup_invocation () ()
Segmentation fault
```
実行すると上記のエラーに遭遇してしまい、うまく起動しませんでした。

## なぜこのような現象に陥ったのか？
まず、graphicsadapterで指定した数値というのは、エンジンの[VulkanRHI.cpp 612行目](https://github.com/EpicGames/UnrealEngine/blob/5.2.0-release/Engine/Source/Runtime/VulkanRHI/Private/VulkanRHI.cpp#L612)で使用されています。
その後、```CVarExplicitAdapterValue```に格納された数値を利用して、VulkanAPIに対応したデバイスを格納する```OriginalOrderedDevices```という配列から指定された数値番目の値を取得し、```Device```および```DeviceIndex```を変数として格納しています。
この```Device```というのが、恐らくエラー発生箇所で使用しているデバイスなどに関係している箇所だと思われるのですが、原因はこの**Device**が何者なのか？という点です。

普通に考えれば、GPUなのかなと思いますので、一度```OriginalOrderedDevices```に格納されている値を見てみましょう。
UE起動時に```OriginalOrderedDevices```に格納されるデバイスについて、[VulkanDevice.cpp 273-276行目](https://github.com/EpicGames/UnrealEngine/blob/072300df18a94f18077ca20a14224b5d99fee872/Engine/Source/Runtime/VulkanRHI/Private/VulkanDevice.cpp#L273-L276)に記述のあるログが吐き出されるため、これを確認してみましょう。

```
[2023.12.22-16.24.47:003][  0]LogVulkanRHI: Display: Found 3 device(s)
[2023.12.22-16.24.47:003][  0]LogVulkanRHI: Display: Device 0:
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - DeviceName: Tesla T4
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - API=1.3.224 (0x4030e0) Driver=0x835a4440 VendorId=0x10de
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - DeviceID=0x1eb8 Type=VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - Max Descriptor Sets Bound 32, Timestamps 1
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: Device 1:
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - DeviceName: llvmpipe (LLVM 11.0.1, 256 bits)
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - API=1.0.2 (0x400002) Driver=0x1 VendorId=0x10005
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - DeviceID=0x0 Type=VK_PHYSICAL_DEVICE_TYPE_CPU
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - Max Descriptor Sets Bound 8, Timestamps 1
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: Skipping device [llvmpipe (LLVM 11.0.1, 256 bits)] of type VK_PHYSICAL_DEVICE_TYPE_CPU (add -AllowCPUDevices to your command line to include it).
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: Device 2:
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - DeviceName: Tesla T4
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - API=1.3.224 (0x4030e0) Driver=0x835a4440 VendorId=0x10de
[2023.12.22-16.24.47:004][  0]LogVulkanRHI: Display: - DeviceID=0x1eb8 Type=VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
```

#### すると、なんてことでしょう...
Device1を見てほしいのですが、こちらが2番目の情報ですね。
```VK_PHYSICAL_DEVICE_TYPE_CPU```とあるように2番目がCPUに関するものだったのです。
```VK_PHYSICAL_DEVICE_TYPE_CPU```が意味するのは、2番目のデバイスがCPUを使用してグラフィックス処理を行うことを示しています。
そして、3番目の値がGPUに関するものでした。
つまり、ここの値の影響でindex + 1しなければ、目的のGPUが使えなかったわけですね。

そして、なんらかの影響でCPUのグラフィックス機能では問題があったことからエラーとなっていたのだと考えられます。

## まとめ
1. GPUの指定は、```-graphicsadapter=${GPUのindex番号}```で行えます。
2. ただし、```graphicsadapter```は、あくまでVulkanAPIに対応したデバイスを取得した際のリストの順番を指定するものであった。


そのため、今まで動かなくて困っていたという方は、もしかしたらGPU以外のデバイスが迷い込んでいるかもしれません。

## 補足事項
ちなみに、今回は特定のGPUを指定して実行、という感じでしたが、マルチGPUで動かす方法がUEでは提供されているみたいです。

たとえば、[nDisplay](https://docs.unrealengine.com/5.3/ja/ndisplay-overview-for-unreal-engine/)を利用することで複数PCや場合によってはマルチGPUに対応することができたり、[NVIDIA SLI Alternate Frame Rendering](https://docs.unrealengine.com/5.3/ja/nvidia-sli-alternative-frame-rendering-in-unreal-engine/)を利用することで複数GPUでレンダリングすることが可能みたいです。
いずれの方法もやりたいことと違っていたり、環境的な制約でうまくできなかったりしたため見送りました。
そのため、最終的には今回のように起動するUEアプリごとにGPUを指定し、振り分けて実行することとなりました。

## 余談
そもそも複数のUEアプリを動かしたときに、1つのGPUのみで6つほど安定して動かせていたのに、2つのGPUに振り分けると4つ動かしたら動きにカクツキが出てきたという現象に出会いました。

結局、時間的にも金額的に採用しないという結論に至ったため詳しい調査できずに終わってしまいましたが、なぜカクツキが出てきたのか調査はしてみたいなと思っています！

UEやGPU（ハード）関係への造詣を深めたいと思う今日この頃です...