PythonでOpenCLを使用する

最近、特にGPUを使用して、並列コンピューティングがしっかりと実施されています。



このトピックについては多くの記事がありますので、技術の表面的な説明にとどまります。 GPGPU-汎用タスク、つまり、GPUの使用 レンダリングに直接関係しません。 例は、いくつかの現代のゲームで物理学を計算するためのNvidia PhysXライブラリです。 このテクノロジーは、GPUが複数のスレッドと並行して実行するのに優れているという点で有益です。 確かに、多くのスレッドがあるはずです。そうしないと、パフォーマンスが大幅に低下します。 それでも、メモリを操作するという特性のため、RAMからビデオメモリへのデータの転送を少し工夫する必要があります。 既知の実装: CUDA (Nvidia、ビデオカードのみ)、 OpenCL (Khronos Group、異種システム用)、およびAMD FireStream ここでは、OpenCLのみについて説明します。



それでは、練習に取りかかりましょう。 メインプログラムの言語としてPythonを選択します。 もちろん、それ自体はそれほど高速ではありませんが、「接着剤」としてはうまく機能します。多くのアプリケーションでは、主な計算はOpenCLで行われ、Pythonコードは「カートリッジを保持する」だけです。 優れたPyOpenCLライブラリがあり、これを使用します。



設置



しかし、まず第一に、OpenCLで作業するために必要なものをすべてインストールする必要があります。 Nvidiaカードの場合、OpenCLサポートはCUDA SDKに付属しているほか、公式ドライバーでもサポートされています。 私の知る限り、nouveauにはまだサポートがありません。 Windowsの場合、公式Webサイトにインストーラーがあります; GNU / Linuxの場合、リポジトリーから必要なソフトウェアをインストールする必要があります。 ArchLinuxの場合、これらはcuda-toolkit、cuda-sdk、nvidia、nvidia-utils、nvidia-openclのパッケージです。 Intelプロセッサには非常に優れたIntel OpenCL SDKがあり、AMDにはネイティブSDKもあります。



CUDA SDKにはoclDeviceQueryの優れた例があり、デフォルトでOpenCLデバイスに関する情報の海を示しています。 全体的なパフォーマンスをチェックするのに非常に便利なこと。



最後のコードは、Python用のライブラリのインストールです。 まず、 NumPy 、次にPyOpenCL自体が必要です。



いくつかの練習ノート



実際、 最近の記事では練習のための多くの重要なことが語られているので、それをよく理解することを強くお勧めします。 ここでは、PyOpenCLを使用する機能に焦点を当てます。



一般に、GPUの主な特徴的な機能に言及する価値があります。 第一に、彼らは多数のスレッドの存在を要求します。そうしないと、パフォーマンスが低下します。 つまり、CPUとは異なり、少し重いスレッドではなく、多くの軽いスレッドを好みます。 第二に、メモリを扱うときは非常に注意する必要があります。 メモリコントローラーは、一度に大量のデータが転送されるという条件でのみ、約束されたGb / sを発行します。 また、アルゴリズムをGPUに転送するときは、キャンプ旅行に行く必要があることに留意する必要があります.GPUからRAMまたはディスクにアクセスしたり、ビデオメモリの大きなブロックを選択したりすることができないため、必要なものはすべて持って行く必要があります。 そして第三に、条件付きジャンプの処理が非常に不十分になったため、最小限に使用する必要があります。



これに基づいて、すべてのアルゴリズムがGPUで適切に機能するとは限らないことに注意してください。 どうやら、画像処理と数学モデリングはビデオカードに最適です。



初期化



OpenCLの操作は、デバイスの初期化から始まります。 公式ドキュメントのページには、create_some_context()を使用して最小限のコードを記述することでデフォルトのデバイスを選択する方法の例があります。 コピーアンドペーストはしませんが、最初に理解しておくと非常に便利です。



ここでは、もう少し複雑なケースを検討します。 特定のデバイスを選択し、さまざまなタイプのパラメーターを渡します。 デバイス(以下「デバイス」はOpenCLデバイス、つまりOpenCLコードが実行される)に転送するための配列は、埋め込みリストまたはタプルにできないことに注意してください。 NumPy配列のみを使用できます。 これは、ライブラリが同じタイプの配列を受け取ることを期待しているためです。



初期化ホストコード
import pyopencl as cl import numpy set_simple_args(model_params) set_device() create_buffers(np.array(particles, dtype=np.float64)) program = cl.Program(self.context, open('worker.cl').read()).build() def set_device(): device = cl.get_platforms()[settings.DEVICE_NUM].get_devices(getattr(cl.device_type, settings.CL_DEVICE))[0] context = cl.Context(devices=[device], dev_type=None) cqueue = cl.CommandQueue(self.context) def set_simple_args(mp): #       numpy #       ,   OpenCL   central_size = numpy.float64(mp['central size']) dt = numpy.float64(mp['dt']) skip = numpy.int32(mp['skip']) q_e = numpy.float64(mp['e charge']) q_i = numpy.float64(mp['ion charge']) ion_m = numpy.float64(mp['ion mass']) def create_buffers(particles): # particles -      mf = cl.mem_flags buf_particles_1 = cl.Buffer(self.context, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=particles) buf_particles_2 = cl.Buffer(self.context, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=particles) buf_charge = cl.Buffer(self.context, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=np.float64(0.0)) buf_q_e = cl.Buffer(self.context, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=np.int32(0.0)) buf_q_i = cl.Buffer(self.context, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=np.int32(0.0))
      
      





ここで何が起こっていますか? まず、「単純な」引数が公開されます-それらは定数として渡されます。 次に、目的のデバイスが構成されます。 settings.DEVICE_NUM-デバイス番号、settings.CL_DEVICE-タイプ(GPU、CPU、またはもっとエキゾチックなもの)。 メモリカードバッファがロードされました。 この手順は、配列と変数に対してのみ実行できます。変数は、カーネルの実行後に値を読み取る必要があります。 最後に、プログラムオブジェクトが(worker.clカーネルソースコード内に)作成されます。



コンパイルエラーが発生すると、例外がスローされ、対応するメッセージが表示されることに注意してください。 また、コンパイラから警告やその他のがらくたを有効にすることもできます。 これを行うには、.bashrcにexport PYOPENCL_COMPILER_OUTPUT=1



追加します。 ここでは、NvidiaのUnixベースのコンパイラとは対照的に、すべてをベクトル化したことを常に明示的に報告するIntelコンパイラに言及できます。



カーネルの起動



ここではすべてが比較的簡単です。 引数は、パラメータとしてカーネル起動関数に渡されます(__call __()を介して)。 このコードでは、新しい反復はそれぞれ新しいカーネルの起動です。



カーネル起動ホストコード
 def iterate(): program.dust_plasma(cqueue, [global_size], None, buf_particles_1, buf_particles_2, buf_charge, buf_q_e, buf_q_i, q_e, q_i, ion_m, central_size, outer_boundary, dt, skip) output_particles, cur_charge = empty_particles, numpy.empty_like(numpy.float64(0.0)) #   output_particles   buf_particles_2 cl.enqueue_copy(cqueue, output_particles, buf_particles_2) cl.enqueue_copy(cqueue, cur_charge, buf_charge) return output_particles, cur_charge
      
      





理解を容易にするために、OpenCLカーネルコードの一部を以下に示します(ご覧のとおり、これはCのようなものです)。



カーネルコードスニペット
 __kernel void dust_plasma(__global double4* particles_1, //      __global double4* particles_2, //    __global double* dust_charge, __global int* charge_new_e, __global int* charge_new_i, __const double charge_e, __const double charge_i, //     __const double ion_mass, //   __const double central_size, // ,  __const double outer_boundary, //  __const double dt, __const unsigned skip) { int id = get_global_id(0); // ... for(unsigned i = 0; i < skip; i++) { //printf("Iteration %i\n", i); params.previous = &particles_1; params.next = &particles_2; one_iteration_rk2(&params); params.previous = &particles_2; params.next = &particles_1; one_iteration_rk2(&params); } barrier(CLK_GLOBAL_MEM_FENCE); // ... }
      
      





おわりに



ドキュメントは非常に適切で便利であることに注意してください。 ライブラリについて質問がある場合は、pyopencl @ tiker.netメーリングリストに投稿できます。コミュニティは非常に活発で、すぐに回答できます。 ライブラリは、リベラルなMITライセンスの下で配布されています。これは非常に便利です。



OpenCL言語自体について-それを学ぶために特別なマニュアルは必要ないと思います。 C99のように、制限を思い出して(もしあれば、コンパイラーが教えてくれる)仕様書に目を通すだけです。 おそらく、事前に知っておくべきことは、float4などの組み込みデータ型です(4つの個別のfloat値で構成される変数は、4つの個別のfloatよりも処理が高速です)。



また、優れたライブラリを提供してくれたAndreasKlöcknerというPyOpenCLの作者にも感謝します。



リクエスト1)テキストのタイプミス/エラーを報告する2)PMで。



関連リンク







編集。 タイプミスを修正。



記事のテキストは、 Creative Commons Attribution-ShareAlike 3.0ライセンス 、コードスニペット-MITでライセンスされています。






All Articles