gpuをjavaで使う話(java casual talks #1)
TRANSCRIPT
CPU● 高機能・高性能・高粒度● OSが実行できる● 演算器はコアあたり10個程度
– 一チップに100個程度● 明示的にメモリを制御できない
– いかにキャッシュに載せるか● = いかにメモリをまとめて扱うか
GPUの構成● いくつかのコアでグループを作る
– 同時に同じ命令を実行する– グループだけからアクセスできるメモリをもつ
● コアのグループが多数ある● コアのグループあたり数個の演算器
– 数千から数万の演算器– グループ内では同じ演算が行われる
● とってもSIMD
GPGPU環境● CUDA
– NVIDIA製品用● DirectX
– Windows専用● OpenCL
– OpenGLとか作った団体(Khronos Group)が策定– さまざまなデバイスで使える– FPGAでも使えたりする
JavaでGPU● Aparapi
– JavaコードをOpenCLに変換● OpenCLを呼び出す
– OpenCL:並列計算フレームワーク● AMD始め、IntelやNVIDIAなどが参加
– JOCL(jogamp.org)– JOCL(jocl.org)– JavaCL
● Project Sumatra– Stream処理を自動的にGPUで行う– Java VMに組み込む
Aparapiコードpublic class AparapiKernel extends Kernel{ float[] inputA; float[] inputB; float[] output; @Override public void run() { int gid = getGlobalId(); output[gid] = inputA[gid] * inputB[gid]; } public static void main(String[] args) { AparapiKernel kernel = new AparapiKernel(); int elementCount = 1_444_477; kernel.inputA = new float[elementCount]; kernel.inputB = new float[elementCount]; kernel.output = new float[elementCount]; fillBuffer(kernel.inputA); fillBuffer(kernel.inputB); kernel.execute(elementCount); }}
バグがある・・・• 三項演算子のカッコが反映されない– (修正してプルリクなげてとりこまれてます)
• CPUとの結果と比較するテストを用意したほうがいい–けど、丸めの違いを考慮するの面倒
void proc(int fxy) { float d = (result[fxy] >= 0 ? 1 : 0) * delta[fxy]; tempBiasDelta[fxy] = learningRate * d;}
void kishida_cnn_kernels_ConvolutionBackwordBiasKernel__proc(This*this, int fxy){ float d = (float)(this->result[fxy]>=0.0f)?1:0 * this->delta[fxy]; this->tempBiasDelta[fxy] = this->learningRate * d; return;}
↓
JOCLのコード String KERNEL_CODE = "kernel void add(global const float* inputA," + " global const float* inputB," + " global float* output," + " uint numElements){" + " size_t gid = get_global_id(0);" + " if(gid >= numElements){" + " return;" + " }" + " output[gid] = inputA[gid] + inputB[gid];" + "}"; CLContext ctx = CLContext.create(); CLDevice device = ctx.getMaxFlopsDevice(); CLCommandQueue queue = device.createCommandQueue(); CLProgram program = ctx.createProgram(KERNEL_CODE).build();
int elementCount = 1_444_477; int localWorkSize = Math.min(device.getMaxWorkGroupSize(), 256); int globalWorkSize = ((elementCount + localWorkSize - 1) / localWorkSize) * localWorkSize; CLBuffer<FloatBuffer> clBufferA = ctx.createFloatBuffer( elementCount, CLMemory.Mem.READ_ONLY); CLBuffer<FloatBuffer> clBufferB = ctx.createFloatBuffer( elementCount, CLMemory.Mem.READ_ONLY); CLBuffer<FloatBuffer> clBufferC = ctx.createFloatBuffer( elementCount, CLMemory.Mem.READ_WRITE);
fillBuffer(clBufferA.getBuffer()); fillBuffer(clBufferB.getBuffer());
CLKernel kernel = program.createCLKernel("add"); kernel .putArgs(clBufferA, clBufferB, clBufferC) .putArg(elementCount);
queue.putWriteBuffer(clBufferA, false) .putWriteBuffer(clBufferB, false) .put1DRangeKernel(kernel, 0, globalWorkSize, localWorkSize) .putReadBuffer(clBufferC, true);
Sumatra● Java VMに組み込むことを目標● 実装難しそう● コード書くのもわかりにくそう● 性能出しにくそう● Java VMに組み込むほどメリットなさそう
– 性能欲しい人はOpenCL使うよね
と思ったら
● 「Sumatra is not in active development fornow.(2015/5/1) 」
http://mail.openjdk.java.net/pipermail/sumatra-dev/2015-May/000310.html
DoubleではなくFloatを使う
● ディープラーニングに精度は不要● 精度が半分になれば並列度があげれる● データの転送量が減る● 75枚/分→95枚/分● 1400万枚の画像処理が130日→102日!
違う処理を並列に行う
● 畳み込み層の逆伝播処理– 重み更新– 誤差伝播– バイアス更新
if(n < deltaCount){ // 誤差伝播}else if (n < deltaCount + filterCount){ // 重み更新}else if (n < deltaCount + filterCount + biasCount){ // バイアス更新}