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 for
now.(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で高速化たのしい
●
世の中ね、顔かお金かなのよ

GPUをJavaで使う話(Java Casual Talks #1)