WebCLプログラミング入門


はじめに

CUDA & OpenCL Advent Calendar 2014の5日目です。
前回、WebCLのデモを動かすでWebCLの環境を導入して動作確認をしましたので、今回はWebCLのプログラムを少し書いてみたいと思います。
Local Memoryの使用など程よくテクニカルな行列積をお題にしようと思います。
なお、OpenCLの知識は前提とさせてもらいます。

PlatformとDeviceの取得

WebCLでは通常のOpenCLと同様に、動作させている環境で使用可能な全てのOpenCL Deviceを選択して使用することができます。
複数のOpenCL Platformがインストールされている場合ももちろん全て使用することができます。

早速、PlatformとDeviceを取得するコードを書いてみます。

function getDevices() {
  var result = [];
  webcl.getPlatforms().forEach(function(platform) {
    platform.getDevices().forEach(function(device) {
      result.push([platform, device]);
    });
  });
  return result;
}

WebCL対応の環境であれば、webclというオブジェクトがグローバルにアクセスできるようになります。
webcl.getPlatforms()でプラットフォームの配列を取得できます。
取得したplatformからplatform.getDevices()でデバイスの配列を取得します。
上のコードでは、[platform, device]というペアの配列を作成しています。

Contextの取得

お次はContextです。

var context;
context = webcl.createContext(); // default context
context = webcl.createContext(device);

webcl.createContext(device)を呼び出すだけです。
引数のdeviceを与えると、デフォルトのDeviceが選択されます。

Kernelの取得

WebCLでのDevice側のプログラムは、OpenCLと同じくOpenCL C言語がそのまま使われます。
行列積を実装するにあたって、カーネル関数はNVIDIAのサンプルからそのまま持ってきてみましょう。
HTMLまたはJSのソース中に埋め込んでもいいですし、AJAXで取得するようにしても構いません。

カーネル関数のソースコードを文字列としてkernelSourceに取得できたら、以下のようにしてKernelを取得します。

var program = context.createProgram(kernelSource);
program.build([device], '-D BLOCK_SIZE=16');
var kernel = program.createKernel('matrixMul');

context.createProgram(kernelSource)でProgramを取得し、program.build(devices, options)でビルドします。
今回持ってきたカーネル関数ではBLOCK_SIZEという定数が必要になっていますので、optionsに渡すことで対応しています。
あとは、必要なKernelをprogram.createKernel(kernelName)で取得します。
ちなみに、OpenCLでのclCreateProgramWithBinaryみたいなものは無いようです。

Bufferの作成

デバイス側のメモリを用意します。

var devA = context.createBuffer(WebCL.MEM_READ_ONLY, bufASize);
var devB = context.createBuffer(WebCL.MEM_READ_ONLY, bufBSize);
var devC = context.createBuffer(WebCL.MEM_WRITE_ONLY, bufCSize);

context.createBufferでサクッと用意します。
bufSizeはバイト数なので、配列の要素数に型のバイト数をかけておく必要があります。
OpenCLの定数関係はWebCLオブジェクトの中にあります。

また、ホスト側はJavaScriptのTyped Arrayを使います。

var A = new Float32Array(ASize);
var B = new Float32Array(BSize);
var C = new Float32Array(CSize);

CommandQueue

CommandQueueを取得します。

var queue = context.createCommandQueue(device);

ホスト・デバイス間のメモリのやり取りは以下の様な感じになります。

queue.enqueueWriteBuffer(devA, false, 0, bufASize, A);
queue.enqueueWriteBuffer(devB, false, 0, bufBSize, B);

Kernel呼び出し

ようやく準備が整ってきたのでKernelを呼び出します。
まずは、引数をセットします。

kernel.setArg(0, devC);
kernel.setArg(1, devA);
kernel.setArg(2, devB);
kernel.setArg(3, new Uint32Array([floatSize * blockSize * blockSize]));
kernel.setArg(4, new Uint32Array([floatSize * blockSize * blockSize]));
kernel.setArg(5, new Uint32Array([wa]));
kernel.setArg(6, new Uint32Array([hb]));
kernel.setArg(7, new Uint32Array([ha]));

このカーネル関数では、1〜3番目の引数がGlobal Memory、4、5番目がLocal Memory、6〜8番目をPrivate Memoryとして受け取ります。
1〜3番目にはBufferをそのまま渡してやります。
4、5番目では、要素数1のUint32ArrayにLocal Memoryのサイズを入れてやることでそのサイズのLocal Memoryが確保されます。
6〜8番目でも、スカラー値の受け渡しに要素数1のTyped Arrayを使います。

そしていよいよ呼び出しです。

queue.enqueueNDRangeKernel(kernel, 2, null, [wa, hb], [blockSize, blockSize]);
queue.finish();

1番目の引数がKernel、2番目の引数がワークアイテムの次元数、4番目と5番目の引数でそれぞれグローバルとローカルのワークアイテム数を指定します。

デモ

以上のコードを整理してゴチャゴチャ付け足して動かせるようにしました。
http://likr.github.io/webcl20141205/

Block Size * Scale次の正方行列の積を計算します。
Repeat回計算した平均時間を表示します。

比較のために、適当に実装したJSでの行列積も置いておきます。
OpenCL DeviceJSを選ぶとこれになります。

function matMulJS(A, B, C, wa, ha, hb) {
  var i, j, k;
  for (i = 0; i < wa; ++i) {
    for (j = 0; j < hb; ++j) {
      var val = 0;
      var iA = wa * i;
      var iB = j;
      for (k = 0; k < ha; ++k) {
        val += A[iA] * B[iB];
        iA += 1;
        iB += ha;
      }
      C[i * wa + j] = val;
    }
  }
}

申し訳程度に実行結果のスクショを載せておきます。

NVIDIAとIntelのOpenCL Platformがインストールされています。
WebCLはメモリ転送も含めて時間を測っているので、サイズが小さいとJS実装よりも遅くなっているんですが、十分に大きいサイズになると数十倍程度の差がでてきます。

おわりに

今回はようやくまともにWebCLプログラムを書いて実行しましたが、十分に大きいデータの処理ではCPU、GPUともにWebCLで性能的なメリットが有ることが確認できました。
また、OpenCLのカーネル関数が修正もなくそのままWebCLで動かせることも確認できました。
WebCLのAPIはOpenCLと若干名前が違ったりしていますが、OpenCLの経験がある人にとってはそれ程苦労なくWebCLプログラムを書くことができるように思います。
もう一回WebCLについて書く予定ですが、WebGL Resource Sharingあたりを触れたいと思います。

Advent Calendarの次の担当は@duxcaさんです。