CPU、FPGA、GPUといったヘテロジニアス・デバイスでプログラムを動作させるにはカーネルと呼ばれる関数をOpenCL C言語で記述する必要があります。このカーネルのコードはPythonのソースコード内で文字列として記述し、OpenCL C言語の文字列を読み込んでホストアプリケーションでビルドまたは、ビルド済みバイナリをロードして実行します。
OpenCL C言語は、C99(ISO/IEC 9899:1999)規格に準拠します。OpenCL-Cの特徴として、カーネルというホストアプリケーションから直接呼び出すことはできません。ホストアプリケーションはカーネルの実行をコマンドキューに挿入することで間接的な呼び出しが可能となります。
まず以下のOpenCL C言語で「01234」と「0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15」という数値を出力するカーネル関数を見てみましょう。
カーネル関数.
__kernel void helloworld(__global uchar16* bow) { printf("input array elements are %d%d%d%d%d\\n", (*bow).s0, (*bow).s1, (*bow).s2, (*bow).s3, (*bow).s4); printf("vector is %v16d\\n", *bow); }
注目頂きたい点としては、__kernelという修飾子が関数の導入箇所にあることです。全てのkernel関数はこの修飾子をつける必要があります。
また戻り値型がvoidとなっていますが、これもカーネル関数の特徴であり、常に戻り値型はvoidとします。最後に引数のbowですが、これはホストアプリケーションの中で初期化したデータをデバイスの管理するメモリ空間にコピーしたものです。
import numpy as np import pyopencl as cl devices = [cl.get_platforms()[0].get_devices(cl.device_type.GPU)[0]] ctx = cl.Context(devices) queue = cl.CommandQueue(ctx) mf = cl.mem_flags bow = np.arange(16).astype(np.uint8) bow_mem = cl.Buffer(ctx, mf.USE_HOST_PTR, hostbuf=bow) program = cl.Program(ctx, """ __kernel void helloworld(__global uchar16* bow) { printf("input array elements are %d%d%d%d%d\\n", (*bow).s0, (*bow).s1, (*bow).s2, (*bow).s3, (*bow).s4); printf("vector is %v16d\\n", *bow); } """).build() kernel = cl.Kernel(program, name="helloworld") kernel.set_arg(0, bow_mem) cl.enqueue_task(queue, kernel)
このように配列の初期化をホストプログラム内で行なっています。
この関数の出力は以下のようになります。
input array elements are 01234 vector is 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
uchar16という16の要素でなる8bit整数を要素に持つベクトルである、bowという変数を取り出して「0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15」という配列を出力できました。
この例ではuchar16というOpenCL-Cのベクトル型が登場しましたが、以降の項目で詳細の解説を行います。
Copyright 2018-2019, by Masaki Komatsu