Red Chainerをコードを変更せずに約2倍くらい早く処理させる

Speee Advent Calendar 2017の5日目の記事です。
4日は僕からGoとポモドーロなGomodoroを一ヶ月運用してみてでした!

はじめに

今回はプライベートで参加しているRed Data ToolsというRuby用のデータ処理ツールを提供するためのプロジェクトで作っているRed Chainerというgemの実行速度をコードを変更せずに約2倍くらい早くするネタ。

Red Chainerとは?

一言で言うとRubyで書かれた深層学習フレームワークです!
github.com

PythonにはChainerと呼ばれる日本製の深層学習フレームワークがあるのですが、これをRubyへポーティングしたものがこのRed Chainerです。
※ ポーティングは気合で手でやってます
Rubyで1から書いていて一部でPythonを外部実行してるみたいなことはしていないです。
ファーストリリースを10月13日に行いまだまだ発展途上のgemです👍

Red Chainerの実行速度について

まずRed Chainerは内部での行列の扱いにNumo::NArrayというgemを使用しています。
このgemの作者の方がRubyKaigi2017で発表されていたProgress of Ruby/Numo: Numerical Computing for Rubyに今回のネタになることが書いてありました。
それが次のスライドです。

ざっくり書くと1コアを使うNumo::NArrayはOpenBLASで4コア使えるNumpyと比べると遅いけどLinalgを使うとOpenBLASが使えるようになって4コア使えるようになるから使うとスピードあがるよ!といった内容(のはず)。
これはやってみる価値がありそうなのでさっそくやってみる!

その前にOpenBLAS? Linalg?

OpenBLASはBLASと呼ばれるベクトルと行列に関する線型代数操作を実行するAPIを提供するものでOpenBLASはそれをオープンソースで提供しているものです。他にもATLASといったものがあるのですがruby-numo/linalgはこのBindingを提供してくれます。

速度比較

今回は誰でも試せるようにDocker Imageにしてそれを使って検証しました。
docker image, Dockerfile

動作環境はEC2インスタンスでvCPU2のt2.medium上でDockerでやってます。

Native Implement

まずは通常のNumo::NArrayで検証した場合。

$ docker run -it hatappi/red-chainer ruby /opt/examples/mnist.rb
Downloading from http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Downloading from http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Downloading from http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Downloading from http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.190656                          0.943517                                 506.072       
2           0.0726881                         0.977267                                 1013.04       
3           0.048782                          0.984417                                 1519.71       
4           0.0380302                         0.987617                                 2025.57       
5           0.0276014                         0.991033                                 2532.41       
6           0.0238486                         0.992383                                 3038.69       
7           0.0217516                         0.99325                                  3544.98       
8           0.0180251                         0.9942                                   4051.35       
9           0.0175256                         0.994517                                 4558.07       
10          0.0158116                         0.995183                                 5107.94       
11          0.012952                          0.996133                                 6240.86       
12          0.0133668                         0.995667                                 7553.72       
13          0.00987872                        0.996933                                 8875.83       
14          0.0132002                         0.99605                                  10177         
15          0.0123773                         0.996183                                 11505.8       
16          0.00862055                        0.9976                                   12860         
17          0.00997421                        0.997067                                 14178         
18          0.00980791                        0.99715                                  15475.7       
19          0.0108061                         0.997033                                 16781.6       
20          0.0109783                         0.996983                                 18122         

約302分・・・・

OpenBLAS with 2cores by loading Linalg

こちらが今回検証対象のOpenBLASのバインディングを提供するruby-numo/linalgをrequireした場合

[root@ip-172-10-2-213 ec2-user]# time docker run -it hatappi/red-chainer ruby -r numo/linalg/use/openblas /opt/examples/mnist.rb
Downloading from http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Downloading from http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Downloading from http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Downloading from http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.18923                           0.942783                                 127.257       
2           0.0760872                         0.97645                                  254.426       
3           0.0481313                         0.984517                                 383.101       
4           0.0352307                         0.988617                                 511.491       
5           0.0284791                         0.99045                                  639.283       
6           0.0246408                         0.992033                                 766.936       
7           0.0213544                         0.992983                                 975.04        
8           0.0195042                         0.9936                                   1515.91       
9           0.0166227                         0.99455                                  2227.03       
10          0.0164481                         0.994817                                 2889.97       
11          0.0133394                         0.996                                    3603.04       
12          0.0111049                         0.996417                                 4275.72       
13          0.0110981                         0.996683                                 4992.94       
14          0.0154447                         0.9956                                   5710.56       
15          0.00854361                        0.997267                                 6374.21       
16          0.00981744                        0.997117                                 7084.47       
17          0.00985091                        0.997233                                 7801.86       
18          0.00997248                        0.996917                                 8477.44       
19          0.00844054                        0.9975                                   9199.59       
20          0.00938205                        0.997467                                 9923.02       

約165分!
半分近くまで早くなってる!

ちなみにPythonのChainer

docker run -it hatappi/chainer python3 mnist.py
GPU: -1
# unit: 1000
# Minibatch-size: 100
# epoch: 20

Downloading from http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz...
Downloading from http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz...
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.193236    0.08251               0.941117       0.9737                    18.3165       
2           0.073018    0.0945126             0.977233       0.9683                    38.1975       
3           0.0478583   0.0625547             0.984767       0.9812                    58.7991       
4           0.0350756   0.0776801             0.989383       0.9789                    80.2456       
5           0.0286255   0.0929291             0.9908         0.9766                    101.535       
6           0.0259772   0.0842944             0.991667       0.9769                    123.005       
7           0.0191559   0.0861707             0.993383       0.9808                    144.731       
8           0.0181119   0.085485              0.9938         0.982                     166.782       
9           0.017674    0.0797785             0.9946         0.9811                    189.257       
10          0.0142222   0.0771581             0.9955         0.9818                    212.275       
11          0.014423    0.0700368             0.995717       0.9829                    235.738       
12          0.0109772   0.0925245             0.996467       0.9812                    259.602       
13          0.0151957   0.0870395             0.995233       0.9792                    283.742       
14          0.00829544  0.0934487             0.9972         0.9807                    308.831       
15          0.0117414   0.08807               0.996567       0.9824                    334.308       
16          0.0101929   0.0885494             0.9968         0.9831                    360.316       
17          0.00932984  0.119531              0.9971         0.9792                    386.981       
18          0.0129252   0.0968258             0.996333       0.9827                    414.402       
19          0.00378327  0.088825              0.999183       0.9841                    442.64        
20          0.0116622   0.107006              0.996833       0.9813                    471.083    

7分w

結果

検証方法 実行時間
Numo::NArray 302分
Numo::NArray
OpenBLAS with 2cores by loading Linalg
165分
Python Chainer 7分

Pythonの実装と比べるとまだまだではあるけど、Numo::NArrayだけで見ると半分くらいまでは短くなっていた :tada:

最後に

今回はコードを書き換えなくても実行時間が短くなる方法を試しました。
Red Chainerにまだまだ改良できるところはあるので、これからコツコツ改善していきたい!

明日はyhattが何かを書くそうです!
お楽しみに!