この記事は、The Parallel Universe Magazine 56 号に掲載されている「oneMKL Random Number Generators Device API in Financial Services Risk Calculation」の日本語参考訳です。原文は更新される可能性があります。原文と翻訳文の内容が異なる場合は原文を優先してください。
柔軟な並列計算能力の必要性
バーゼル銀行監督委員会 (BCBS) (英語) は、金融リスク軽減策の必要性の高まりにより、計算需要が 4~20 倍に増加すると予測しています。これには、気候変動、サプライチェーンの混乱、地政学的な変化に関連する、資産の変動を深く理解するための高度な市場予測アルゴリズムとリスク・シミュレーションが含まれます。銀行および金融サービス業界 (FSI) のソフトウェア開発者は、GPU ベースのアクセラレーションを備えたハイパフォーマンス・コンピューティング (HPC) 環境を利用して、リスク・シミュレーション・モデルを実装しています。これらのワークロードは広範にデプロイされるため、さまざまなハードウェア・プラットフォーム構成で実行できることが重要です。
Unified Acceleration (UXL) Foundation (英語) の取り組みが支持され SYCL* (英語) の人気が高まるまでは、ワークロードを新しい環境にデプロイするたびにコードを移植してリファクタリングする必要があり、大きな課題と非効率性に直面していました。
モンテカルロ法は金融分野でよく知られた手法であり、シミュレーション・シナリオのシードとして高性能の乱数生成に依存しています。特にエキゾチック・オプションでは、予想投資利益率 (ROI) が複雑すぎて直接計算できないことが多いため、オプションの価格付けに使用されます。アメリカン・オプション・モデルとヨーロピアン・オプション・モデルは、さまざまなオプション投資結果の確率予測に広く使用されている 2 つのモンテカルロ・シミュレーション手法です。
2020年に C++ with SYCL* ベースの oneAPI マス・カーネル・ライブラリー (oneMKL) インターフェイス (英語) が登場して以来、oneAPI 仕様のこのコンポーネントとインテル® oneAPI マス・カーネル・ライブラリー (英語) のオープンソース拡張機能により、乱数ジェネレーター (RNG) ルーチン向けのデータ並列 C++ インターフェイス (英語) が提供され、よく使用される擬似乱数、準乱数、連続分布と離散分布を備えた非決定性生成器が実装されています。
ほかの oneMKL 関数ドメインと同様に、oneMKL RNG には実装の一部として次のものが含まれます。
- 手動オフロード機能 (乱数ジェネレーター・ホスト API)
- デバイス機能 ― SYCL* カーネルから直接呼び出し可能な関数群 (乱数ジェネレーター・デバイス・ルーチン (英語))。
この記事では、oneMKL 乱数ジェネレーター (RNG) デバイス API を使用して、インテル® データセンター GPU 上での計算パフォーマンスをホスト API 実装の 2 倍に大幅に向上する方法を示します。
ヨーロピアン・オプション価格付けモデルとアメリカンオプション価格付けモデルのホスト API とデバイス API の RNG 関数呼び出しのパフォーマンスを測定して比較します。
さらに、モンテカルロ・アルゴリズムを使用したアメリカンオプション価格付けモデルを例として使用し、金融ワークロードを独自の CUDA* コードから SYCLomatic (英語) (またはインテル® DPC++ 互換性ツール) を使用してオープンなマルチプラットフォーム SYCL* プログラミング・モデルに移行する方法を詳しく説明します。
この記事で紹介する結果は、インテル® データセンター GPU Max 1550 上で oneAPI 2024.0 リリースを使用して得られたものです。
RNG デバイス API の基本
デバイス・インターフェイスの主な目的は、SYCL* カーネルから呼び出せるようにすることです。送信時間はアプリケーションの全体的な実行時間に大きな影響を与えるため、パフォーマンスが大幅に向上します。グローバルメモリー転送のコストを排除し、素早く乱数を取得し、同じカーネル内で乱数を処理します。次に例を示します。
ホスト API
auto engine = oneapi::mkl::rng::mrg32k3a(*stream, 1ull); oneapi::mkl::rng::generate(oneapi::mkl::rng::gaussian<double>(0.0, 1.0), engine, n, d_samples); sycl_queue.parallel_for( sycl::nd_range<3>(sycl::range<1>(n_groups) * sycl::range<1>(n_items), sycl::range<1>(n_items)), [=](sycl::nd_item<1> item_1) { post_processing_kernel(d_samples); }).wait();
デバイス API
sycl_queue.parallel_for( sycl::nd_range<3>(sycl::range<1>(n_groups) * sycl::range<1>(n_items), sycl::range<1>(n_items)), [=](sycl::nd_item<1> item_1) { oneapi::mkl::rng::device::mrg32k3a<1> engine_device(1ull, n); oneapi::mkl::rng::device::gaussian<double> distr(0.0, 1.0); double rng_val = oneapi::mkl::rng::device::generate(distr, engine_device); post_processing_kernel(rng_val); }).wait();
上記のホスト API コードシーケンスは、CPU から GPU への呼び出しを開始する 2 つのインスタンスを示しています。
- 少なくとも 1 つの SYCL* カーネルを含む generate() 関数呼び出し
- nd_range および post_processing_kernel を含む、オフロードされた parallel_for ループカーネル
デバイス API コードシーケンスでは、これらがすべて単一のオフロードされた parallel_for SYCL* カーネル内に含まれており、CPU と GPU 間のデータ交換が最小限になります。
そのため、デバイス API を使用すると、実装に必要なカーネルの数がホスト API よりも少なくなります。RNG デバイス API は、GitHub* にあるオープンソース・プロジェクト、oneMKL インターフェイス (英語) の一部としても利用できます。