gpuをjavaで使う話(java casual talks #1)

29
GPUをJavaで使う話 (来れたので) 2015/10/16 きしだ なおき

Upload: -

Post on 16-Apr-2017

3.037 views

Category:

Software


4 download

TRANSCRIPT

GPUをJavaで使う話(来れたので)

2015/10/16 きしだ なおき

自己紹介

● 最近、化物語を見ました。

今日の話

● GPU速そう● Cめんどい● Javaでやりたい● ディープラーニング書いてみた● 高速化してみた

CPU● 高機能・高性能・高粒度● OSが実行できる● 演算器はコアあたり10個程度

– 一チップに100個程度● 明示的にメモリを制御できない

– いかにキャッシュに載せるか● = いかにメモリをまとめて扱うか

GPU● GPU

– ちょうたくさんコアがある– 同じ処理を行う– 行列計算に向いてる

● GTX 970

– 1664コア!– 衝動買い!

GPUの構成● いくつかのコアでグループを作る

– 同時に同じ命令を実行する– グループだけからアクセスできるメモリをもつ

● コアのグループが多数ある● コアのグループあたり数個の演算器

– 数千から数万の演算器– グループ内では同じ演算が行われる

● とってもSIMD

GPUの製品● NVIDIA

– GeForce– Tesla

● GPGPU専用● AMD

– Radeon● Intel

– Intel HD Graphics● CPUチップに内蔵されたGPU

GPGPU● General Purpose computing on GPU● GPUで汎用計算● シミュレーションとか速い● 最近のスーパーコンピュータはGPUがたくさん載って

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

● A PARalell API● 実行時にJavaコードをOpenCLに変換● https://code.google.com/p/aparapi/

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(jogamp.org)

● OpenCLを薄くラップ● https://jogamp.org/jocl/www/

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);

比較● Aparapi

– めちゃ楽– GPUの性能出しにくい

● JOCL– ちょっと面倒– GPUの性能出しやすい

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

ところでディープラーニング実装してみました

ディープラーニング

● 階層の深いニューラルネット● 最近、人工知能っていわれてるのは、ほぼこれ

Aparapiを使う

● 15枚/分→75枚/分● 1400万枚の画像処理が650日→130日!

DoubleではなくFloatを使う

● ディープラーニングに精度は不要● 精度が半分になれば並列度があげれる● データの転送量が減る● 75枚/分→95枚/分● 1400万枚の画像処理が130日→102日!

JOCLを使う

● 95枚/分→298枚/分● 1400万枚の画像処理が102日→34日!

GPUローカルメモリを使う

● 298枚/分→300枚/分● 1400万枚の画像処理が34日→33日

違う処理を並列に行う

● 畳み込み層の逆伝播処理– 重み更新– 誤差伝播– バイアス更新

if(n < deltaCount){ // 誤差伝播}else if (n < deltaCount + filterCount){ // 重み更新}else if (n < deltaCount + filterCount + biasCount){ // バイアス更新}

条件分岐の注意

● ひとつのグループにif/else両方走るとif→elseの順に両方の処理が走る

● ひとつのグループではどちらかになるように調整が必要

● 32個単位くらい

画面表示をはぶく

● 画面表示のためにGPU→CPU間転送が行われていた

● 300枚/分→320枚/分● 1400万枚の画像処理が34日→30日

結果

● 機械学習– まだ学習できてません・・・

● オレ学習– GPUのプログラミングが学習できました。

まとめ

● GPUで高速化たのしい● 世の中ね、顔かお金かなのよ