まだ処理系も無いのにフライング気味にOpenCL仕様書の私的解釈を述べてみたいと思います。非常に部分的な説明ですがご容赦ください。例の如く、勘違いや問題等あればご指摘いただければ幸いです。
ホストとデバイス、コマンドキューについて
OpenCLにおいて、OpenCL APIを呼び出し計算を指示する方をホスト(host)、計算を行う装置をデバイス(device)と呼びます(仕様書ページ18)。ホストではOpenCLリソースを管理するコンテキストを生成し、さらにホスト側からOpenCLを通じてデバイス操作(計算を実行する、メモリ操作をする、同期をとる)を調停するためのコマンドキューを生成します(仕様書ページ21)。コマンドキューはインオーダ実行、アウトオブオーダ実行ともにサポートしています(仕様書ページ22)。
カーネルについて
OpenCLでは、C言語でいうところの関数のような処理単位をカーネル(kernel)と表現します。しかし、カーネルは並列で実行される前提であり、特定の計算の一部分のみを計算するように記述すべきでしょう。これはデバイスで並列処理を行う為です。
OpenCLにおいて、カーネルはワークアイテム(work-item)として、N次元のインデックス空間(index space)で(例えば2次元で4,5と指定すれば4×5の計20アイテムが)実行されます。そしてワークアイテムにはグローバルIDが設定されます。これはC言語における配列と同様に0からサイズ-1の範囲で、かつ各インデックス空間次元ごとの直積のようになるので、先述の例ではそれぞれのワークアイテムに対し(0,0),(0,1),(0,2),(0,3),(1,0),(1,1),(1,2),(1,3),(2,0),(2,1),(2,2),(2,3),(3,0),(3,1),(3,2),(3,3),(4,0),(4,1),(4,2),(4,3)が割り当てられます。
さらにOpenCLにはワークグループ(work-group)という概念があります。これは複数ワークアイテムをグループ化するものです。このとき各ワークグループにはワークグループID、ワークグループ内ワークアイテムにはローカルIDが割り当てられます。
例えばワークアイテムが2次元で6×3=18だけ実行し、ワークグループ内ワークアイテム数を2×3=6とした場合、グローバルIDとワークグループIDおよびローカルIDの対応は、
| グローバルID | ワークグループID, ローカルID |
|---|---|
| (0,0) | (0,0),(0,0) |
| (0,1) | (0,0),(0,1) |
| (0,2) | (0,0),(0,2) |
| (1,0) | (0,0),(1,0) |
| (1,1) | (0,0),(1,1) |
| (1,2) | (0,0),(1,2) |
| (2,0) | (1,0),(0,0) |
| (2,1) | (1,0),(0,1) |
| (2,2) | (1,0),(0,2) |
| (3,0) | (1,0),(1,0) |
| (3,1) | (1,0),(1,1) |
| (3,2) | (1,0),(1,2) |
| (4,0) | (2,0),(0,0) |
| (4,1) | (2,0),(0,1) |
| (4,2) | (2,0),(0,2) |
| (5,0) | (2,0),(1,0) |
| (5,1) | (2,0),(1,1) |
| (5,2) | (2,0),(1,2) |
となります(仕様書ページ20, 21)。
すなわち、カーネルのOpenCLプログラムではグローバルIDおよびローカルIDをもとに、全く同じプログラムながらも並列に異なる計算を行うことができるのです。
カーネルの実行
kernelを実行するためのホスト側OpenCL APIがclEnqueueNDRangeKernel関数です。これはカーネル実行コマンドをコマンドキューにエンキューします(仕様書103ページ)。
cl_int clEnqueueNDRangeKernel (cl_command_queue command_queue, cl_kernel kernel, cl_uint work_dim, const size_t *global_work_offset, const size_t *global_work_size, const size_t *local_work_size, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event);
各引数について説明します。command_queueにはこのカーネルをエンキューするコマンドキューを、kernelはカーネルを指定します。
work_dimには先述のインデックス空間の次元数を指定します。現状の仕様では1か2か3かのいずれかのみとなっているようです。
global_work_offsetはインデックス空間におけるグローバルIDの開始インデックスを指定する...ようですが、現在の仕様ではNULL固定です。
global_work_sizeにはインデックス空間各次元におけるワークアイテム数を配列で指定します。例えばwork_dimが2で、4×5だけカーネルを実行する場合、size_t w_size[] = {4,5}のような配列を与えます。同様にlocal_work_sizeはワークグループ内のワークアイテム数を指定します。NULLの場合OpenCL実装が適当にカーネルをワークグループに分割します。
num_events_in_wait_listおよびevent_wait_listには、このコマンドを実行する上で待機したいイベントの数および配列のアドレスを指定します。待機する必要がない場合、0およびNULLを指定します。
eventは出力用引数で、このカーネル実行そのものを示すイベントを返します。
clEnqueueNDRangeKernel関数を簡略化したものがclEnqueueTask関数です。これは単一ワークアイテムのみのカーネル実行コマンドをエンキューします(仕様書106ページ)。
cl_int clEnqueueTask (cl_command_queue command_queue, cl_kernel kernel, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event);
引数はclEnqueueNDRangeKernelのものと等価で、動作そのものもclEnqueueNDRangeKernelにおいてwork_dimを1、global_work_offsetをNULLにglobal_work_size[0]およびlocal_work_size[0]を1としたものと等価です。
ベクトル加算プログラムの説明
実例として、任意次元のベクトルの加算をOpenCLで考えてみます。これはhttp://www.khronos.org/opencl/presentations/OpenCL_Summary_Nov08.pdfの"Vector Addition"にも示されています。
__kernel void vec_add (__global const float *a,
__global const float *b,
__global float *c)
{
int gid = get_global_id(0);
c[gid] = a[gid] + b[gid];
}
このカーネルvec_addはほぼC言語における関数として解釈して差し支えありません。つまり、float型配列a,b,cを受けとり、変数gidを元に、aの要素およびbの要素の加算結果をcの要素に代入します。
ここでポイントは、変数gidにget_global_id(0)の戻り値を代入している点です。この関数get_global_idは仕様書の"6.11.1 Work-Item Functions"(ページ159)に定義されており、現在のワークアイテムのグローバルIDのうち、引数の次元の値を返します。すなわちこのカーネルのワークアイテムが1次元でn個実行された場合、get_global_id(0)の戻り値はそれぞれのワークアイテムで0からn-1の固有の数値となります。
以上の事からvec_addカーネルを同一引数で、1次元n個だけ並行に実行する事でn次元ベクトルの加算が実現できるのです。
終わりに
ここではカーネルについてのみ記述しましたが、Latest SC08 Overviewを見る限り、実際に計算を行うにはメモリ構成やメモリ入出力について、またコンテキスト生成やバッファ生成、プログラムビルドなどの前処理や、計算結果の取得のような後処理が必要なようです。この記事では触れませんでしたが今後別記事にて記述するかもしれません。
- 参考文献
- OpenCL 1.0 Specification (http://www.khronos.org/registry/cl/specs/opencl-1.0.29.pdf)
- Latest SC08 Overview (http://www.khronos.org/opencl/presentations/OpenCL_Summary_Nov08.pdf)
カテゴリ
トラックバック
このブログ記事を参照しているブログ一覧: OpenCL概要:ホストとデバイス、カーネルについて
このブログ記事に対するトラックバックURL: http://www.memorize-being.net/x/mt-engine/mt-tb.cgi/52


コメント
ただいまOpenCLでサンプルを書きつつ勉強していて、参考になりました。ありがとうございました。
コメントする