OpenMP* でグラフィックス・デバイスを選択して実行する

インテル® oneAPI

前回の記事では、SYCL* で統合グラフィックス (iGPU) とディスクリート・グラフィックス (dGPU) を選択的に使用する方法を紹介しました。SYCL* は C++ ベースのプログラムでしか利用できないため、C や Fortran ユーザーは歯がゆい思いをしていたことでしょう。この記事では、OpenMP* を使用して複数のグラフィックス・プロセッサーを選択して実行するにはどのようにするか簡単に紹介します。

現在、インテル® DPC++/C++ コンパイラーおよびインテル® Fortran コンパイラーの OpenMP* オフロード機能を使用してディスクリート・グラフィックス・デバイスに処理をオフロードするには注意事項があります。統合グラフィックス (iGPU) を搭載しないインテル® Xeon® スケーラブル・プロセッサーや BIOS で iGPU を無効化している場合は問題ありません。

期待するグラフィックス・デバイスに処理をオフロードできない

iSUS でディスクリート・グラフィックスへのオフロードの検証中に遭遇した問題を紹介します。次の 2 つのシステムにインテル® Arc™ A380 グラフィックス・カードを装着しました。

  • システム 1: インテル® Core™ i7 11700K プロセッサー (インテル® UHD 750 グラフィックス) ベースの Windows* 11
  • システム 2: インテル® Core™ i7 12700K プロセッサー (インテル® UHD 770 グラフィックス) ベースの Windows* 11

どちらのシステムも BIOS でグラフィックスは PCIe* デバイスを優先し、iGPU を有効にしています。Windows* 上のタスク マネージャーで確認すると、それぞれ GPU0 と GPU1 として認識されています。


図1: システム 1 でタスクマネージャーを実行


図 2: システム 2 でタスクを実行

この状況で、コマンドプロンプトからオフロード機能を使用する OpenMP* プログラムを実行すると、システム 1 ではインテル® Arc™ A380 グラフィックスで実行され、システム 2 ではインテル® UHD グラフィックス 770 で実行されました。インテル® コンパイラーの OpenMP* ランタイムや SYCL* ランタイムは、デバイドライバーが識別したデバイス情報を参照します。その際内部テーブルの上位にあるグラフィックス・デバイスがデフォルト・グラフィックス・デバイスとして識別されると考えられます。

両方のシステムで SYCL* を使用したプログラムは、選択的にデバイスを使用できます。sycl-ls コマンドで利用可能なデバイスを確認してみます。OpenMP* でもこのような対応コマンドが利用できればいいのですが …


図 3: システム 1 で sycl-ls を実行


図 4: システム 2 で sycl-ls を実行

両者を比べると、インテル® UHD7XX グラフィックスとインテル® Arc™ A380 グラフィックス・デバイスの識別順序が入れ替わっているようです、おそらく OpenMP* ランライムも同様の識別を行っているのではないでしょうか? つまり、最初 (上位) に識別されているグラフィックス・デバイスが OpenMP* のデフォルト・グラフィックスであると考えると納得できます。そこで、システム 2 の BIOS 設定で iGPU を無効に設定したところ、システム 2 でも dGPU にオフロードできるようになりました。

現在、インテル® DPC++/C++ コンパイラーおよびインテル® Fortran コンパイラーは、iGPU と dGPU への OpenMP* オフロードをサポートしていますが、環境変数またはプログラムの API からグラフィックス・デバイスを選択できないようです。詳細はインテル社に確認中ですので、確認でき次第この記事を更新する予定です。

デバイスに関連する OpenMP* オフロード機能

OpenMP* オフロードに関連する重要な環境変数とランタイム API には、以下があります。

環境変数:
OMP_TARGET_OFFLOAD: オフロードの動作を制御します (MANDATORY/DISABLED/DEFAULT)。
OMP_DEFAULT_DEVICE: デフォルトで使用するターゲットのデバイス番号を指定します。
LIBOMPTARGET_DEBUG: デバッグ情報を出力します。

API:
omp_get_num_devices(): ホスト以外に利用可能なデバイスの数を返します。
omp_get_default_device(): デフォルトで使用するターゲットのデバイス番号を返します。
omp_get_initial_device(): ホストから呼び出されると 1 を、デバイスから呼び出されると -1 を返します。

OpenMP* オフロードの動作を検証

次の簡単なプログラムを使用してオフロードの動作を検証します。節や API の使い方に関する詳細はこちらのシンタックス・リファレンス・カードを参照してください。

#include <stdio.h>
#include <omp.h>

int val_omp_get_num_procs=0, val_omp_get_initial_device=0;

int main(){
printf("omp_get_num_procs = %d デバイスで利用可能なプロセッサー数\n", omp_get_num_procs());
printf("omp_get_num_device = %d ホスト以外のターゲットデバイス数\n", omp_get_num_devices());
printf("omp_get_default_device = %d デフォルトで使用するターゲットデバイス番号\n", omp_get_default_device());
printf("omp_get_initial_device = %d ホストで実行されている場合 1 を返す\n", omp_get_initial_device());
printf("\n\n");

printf("ここから target 領域\n");
// omp_set_default_device(0);
#pragma omp target map(from: val_omp_get_num_procs, val_omp_get_initial_device)
{
   val_omp_get_num_procs = omp_get_num_procs();
   val_omp_get_initial_device =  omp_get_initial_device();
}
printf("omp_get_num_procs = %d デバイスで利用可能なプロセッサー数\n", val_omp_get_num_procs);
printf("omp_get_initial_device = %d ホストで実行されている場合 1 を返す\n", val_omp_get_initial_device);
}

OpenMP* の target 領域 (つまりデバイス) からは、printf の呼び出しがサポートされていますが、日本語 (漢字かななど) を含んだ文字列は正常に動作しない可能性があるので注意してください。そのため、ここでは変数に API の戻り値を格納しています。

それでは、プログラムの説明をしていきましょう。7 行目の omp_get_num_procs() は、ホストで利用可能な論理プロセッサー数を返します。ここではデバイスの EU 数ではありません。この値は OMP_NUM_THRAEDS 環境変数を設定しても変わりません。

8 行目は重要です。omp_get_num_devices() は、ホストで使用可能なアクセラレーター・デバイスの数を返します。iGPU と dGPU が利用可能であれば、2 が返されると期待されます。しかし、現在は 1 が返ります。

9 行目の omp_get_default_device() では、デフォルトのターゲットデバイス番号を取得しています。通常インテルの OpenMP* ランタイムは、グラフィックス・デバイスにデバイス番号 0 を、CPU に 1 を割り当てます。つまり、omp_get_default_device() で取得したデバイスがオフロードのターゲットデバイスとなります。この値は、OMP_DEFAULT_DEVICE 環境変数を使用して変更できます。

10 行目の omp_get_initial_device() は、この呼び出しがホストデバイスで実行されているか、グラフィックス・デバイスで実行されているかを示します。ホストデバイスで実行されると 1 が返ります。この API を使用して、グラフィックス・デバイスで実行するコード領域を制御することができます。

16 行から 19 行の target 構造は、ターゲットデバイスで実行されます。ここでは、omp_get_num_procs()omp_get_initial_device() API を呼び出して、利用可能な EU 数とイニシャルデバイスを取得しています。前述のように target 領域では日本語表示ができないため、変数に値を格納します。結果は target 構造外の 20 行 と 21 行で表示します。ターゲット領域では呼び出しが許されていない API (omp_get_default_deviceomp_get_num_devices など) がありますので注意してください。詳細は OpenMP* 仕様 (英語) で確認してください。

上記のソースを icx omp_api.c /Qopenmp /Qopenmp-targets=spir64 でコンパイルして実行すると、次のような結果が得られます。


図 5: サンプルプログラムの実行例

ホストコードが利用できる論理プロセッサー数は 20、ターゲットデバイス数は 1、デフォルトのターゲットデバイス番号は 0 、そしてこのコードはホストで実行されていることが分かります。

次に target 領域で取得した情報を確認します。インテル® Arc™ A380 のXe-HPG-core には、16 個のベクトルエンジンと 16 個の行列エンジンが搭載されています。Xe-HPG GPU は 8 つの Xe-HPG-slice で構成され、8 つの Xe-HPG-core があります。つまり、omp_get_num_procs で返る値は、16 x 8 x 8 = 1024 になります。インテル® Arc™ A380 アーキテクチャーの詳細は、『oneAPI GPU 最適化ガイド (日本語版)』をご覧ください。omp_get_initial_device() の戻り値は -1 であるため、ホストではないデバイスで実行されたことが分かります。

OMP_TARGET_OFFLOAD 環境変数でオフロード制御する

OMP_TARGET_OFFLOAD 環境変数に、default (デフォルト)、mandatory (オフロード必須)、disabled (オフロード無効) を指定して実行すると次のようになります。


図 6: OMP_TARGET_OFFLOAD 環境変数を設定して実行する

システムにインテル® Arc™ グラフィックス・デバイスが装着されているため、defaultmandatory の結果は同じになります。disabled の場合 target 領域も CPU で実行されるため omp_get_num_procs では論理プロセッサー数が返されます。ここで注意が必要なのは、omp_get_initial_device に 0 が返っていることです。オフロードを無効化、つまり GPU はないものと扱っているようで、1 (ホストで実行) や -1 (GPU で実行) でもない値が返るようです。この辺りは実装依存であるため、希望としては 1 が返ってほしいものです。

OMP_DEFAULT_DEVICE でデフォルトデバイスを変更してみる

OMP_DEFAULT_DEVICE 環境変数では、target ディレクティブが device 節を持たない場合の、デフォルトデバイスを指定することができます。上記ではデフォルトのターゲットデバイス番号 0 (GPU) で実行されています。ここで、OMP_DEFAULT_DEVICE=1 (ターゲットをデバイス番号 1、つまり CPU) に設定して実行すると、target 領域は新たに設定されたデフォルトのターゲットデバイス番号 (CPU) で実行されます。


図 7: OMP_DEFAULT_DEVICE 環境変数でデフォルトデバイスを変更する

OMP_DEFAULT_DEVICE 環境変数に存在しない (誤った) デバイス番号を設定すると、実行エラーが発生します。次の例では、0 が dGPU、1 が CPU なら 2 は iGPU だろうと想定して OMP_DEFAULT_DEVICE=2 を設定して実行したものです。エラー「Device 2 is not ready」が報告され、LIBOMPTARGET_DEBUG 環境変数を設定して詳細を確認することが促されています。


図 8: OMP_DEFAULT_DEVICE 環境変数で誤ったデバイス番号を指定した場合

LIBOMPTARGET_DEBUG を設定してデバッグ情報を取得する

LIBOMPTARGET_DEBUG は、インテル® コンパイラーの拡張環境変数であり、OpenMP* ランタイムから各種情報を取得することができます。1 を設定すると基本的なランタイム情報が、2 を設定するとターゲットデバイスのランタイム情報を取得できます。デバッグ情報は stderr (標準エラー出力) に出力されるため、プログラムが出力するメッセージを混在させたくない場合はリダイレクションするとよいでしょう。

図 8 で発生した状況を LIBOMPTARGET_DEBUG=1 を設定して確認してみます。デバッグ情報とは直接関連ありませんが、情報の最初に認識されている GPU デバイスに関する情報が示されています。GPU デバイスとしてインテル® Arc™ A380 グラフィックス・デバイスが認識され、デバイスに関する情報を確認できます。


図 9: デバッグ情報の出力例

では、図 10 で実際にエラーを確認してみましょう。次のテキストが出力されています。

Libomptarget --> Checking whether device 2 is ready.
Libomptarget --> Device ID 2 does not have a matching RTL
Libomptarget --> Device 2 is not ready.

デバイス番号 2 が利用できるか確認したところ、RTL (ランタイム・ライブラリー) と ID が一致しないため、「デバイスは利用可能状態ではない」と判断されていることが分かります。

この他にも、OpenMP* ランタイムが API を呼び出したタイミングや、使用するメモリーのサイズと転送情報などプログラムの最適化にも役立つ多くの情報が提供されます。デバッグや最適化に活用してください。


図 10: エラーの現認を調査

OpenMP* のオフロード機能を使用して iGPU、dGPU そして CPU が利用できることを確認しましたが、まだまだ確認すべきことがたくさんあります。

  • 同種のインテル dGPU が複数搭載されている場合の動作
  • 異なるインテル dGPU (380 と 770 など) が搭載されている場合の動作
  • SYCL* と OpenMP* を同時に使用した場合の動作

今後確認でき次第、記事で紹介していきますのでご期待ください。

タイトルとURLをコピーしました