verilog書く人

自称ASIC設計者です。どなたかkaggle一緒に出ましょう。

CupyとNumpyのパフォーマンスを比較してみた

GPUで、Numpy互換のAPIで行列計算ができるCupyは活発に更新されています。

 

sortやinv、最近はsparseまで、numpy(とscipy)の機能の多くをカバーするようになってきて、numpyの代用になりえるものになってきたと思います。

 

そこでどれだけの機能がサポートされているのか、そして、GPUで計算することにより、どれだけ、速くなるのか?調べてみました。

 

 サポートされているAPI数の比較

ざっくりとした調べ方になりますが、inspect.getmembers()でcupyとnumpyのメンバ数を調べてみます。

 

>>> import inspect

>>> import cupy

>>> import numpy

>>> len([mem for mem in inspect.getmembers(numpy) if inspect.isfunction(mem[1])])
269

>>> len([mem for mem in inspect.getmembers(cupy) if inspect.isfunction(mem[1])])
121
 

ということで、numpy.***のメンバは269存在するのに対し、cupy.***のメンバは121存在するので、だいたい半分くらいの機能がサポートされている、ということになります。

 

cupyがまだサポートしていないnumpyのAPI

 

>>> numpy_functions = [mem[0] for mem in inspect.getmembers(numpy) if inspect.isfunction(mem[1])]
>>> cupy_functions = [mem[0] for mem in inspect.getmembers(cupy) if inspect.isfunction(mem[1])]
>>> only_numpy = [function for function in numpy_functions if function not in cupy_functions]

>>> print(only_numpy)

 

でリストアップすることができます。(長いので記事には書きませんが。)

 

計算時間の比較条件

続きまして、肝心の計算時間を比較してみます。

 計測条件

OS: Ubuntu 16.04

CPU: Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz

CPUメモリ: 12GB

GPU: GeForce GTX 1080 (8GB)

Python: 3.5.3

Numpy 1.13.3 (OpenBLAS)

Cupy 4.0.0b1 (CUDA 8.0)

 

cupyは最初に使う関数はコンパイルされて計算時間が長くなりますが、以後はコンパイル結果がキャッシュされるので、キャッシュ入手後の状態で計測しています。

CUDAが微妙に古いですが、 諸事情で仕方なし。CUDA9使えばよりCupy有利な計算時間じなります。

 

numpyの計算時間はtimeモジュールを使って計測します。

def meas_numpy(func, operand):
    start = time.time()
    for i in range(cnt):
        func(operand, xp=numpy)
    end = time.time()
    elapsed = (end - start) * 1000 / cnt
    print("numpy elapsed {} msec".format(elapsed))

cupyはcupy.cudaを使って計測する必要があります。

def meas_cupy(func, operand):
    stream = cupy.cuda.Stream.null
    start = stream.record()
    for i in range(cnt):
        func(operand, xp=cupy)
    end = stream.record()
    end.synchronize()
    elapsed = cupy.cuda.get_elapsed_time(start, end) / cnt
    print("cupy elapsed {} msec".format(elapsed))

timeとは異なり、 cupy.cuda.get_elapsed_time(start, end)の出力はmsecであることに注意。

計測スクリプトの全文はNumpy VS. Cupy · GitHubに置きました。

 

計算時間の比較結果

基本的にGPUは並列して計算ができればできるほど(CPUと相対的には、)速く動作します。

ですので、行列の要素数を変えながら計算時間調べました。

 

elementwise系

add    
Array size Numpy Cupy
100 0.001142025 0.0580332804
10000 0.0073647499 0.0542643213
1000000 1.8541741371 0.0689798403
10000000 22.4229431152 0.7049964905

 

sub    
Array size Numpy Cupy
100 0.0010585785 0.0562422419
10000 0.0072026253 0.0496534395
1000000 1.7659640312 0.063638401
10000000 21.4246702194 0.6445593262

 

sin    
Array size Numpy Cupy
100 0.0036048889 0.0632198381
10000 0.2651715279 0.0551119995
1000000 25.3970527649 0.1261561584

 

さすがにElementwiseな計算は得意そうです。10000要素以上の行列では圧倒的にcupyが速い。sinがやたら速い。

reduce系 

sum    
Array size Numpy Cupy
100 0.0047969818 0.0781324816
10000 0.0136995316 0.0716195202
1000000 0.7352280617 0.5519955063
10000000 7.388613224 5.5220483398

 

argmax    
Array size Numpy Cupy
100 0.0009727478 0.0792086411
10000 0.0113844872 0.0711791992
1000000 1.1310005188 0.7271334076
10000000 11.6488671303

7.236550293

 

さすがにreduce演算は苦手そうです。これらの演算が主体の計算はcupyではあまり速くならないでしょう。


行列積系

tensordot    
Array size Numpy Cupy
100 0.0173902512 0.1158998394
10000 0.2496337891 1.7215263367
1000000 1387.7195715904 74.6218798828

 

matmul    
Array size Numpy Cupy
100 0.0046825409 0.4738044739
10000 0.4622888565 0.4494166565
1000000 43.664329052 12.9769299316

 

einsum    
Array size Numpy Cupy
100 0.0066685677 0.7908892822
10000 0.192565918 1.0777776337
1000000 31.0439896584 2.9332293701

 

速くなるのは1000000要素を越えてからのようです。einsumは色々できるのですが、今回は'...i,...j->...ij'の計算をしました。

 

残念ながら場合によってはnumpyよりも遅くなる演算もあります。

その他

transpose    
Array size Numpy Cupy
100 0.0105547905 0.0067788798
10000 0.0125741959 0.0110348797
1000000 0.0111436844

0.0131529605

配列のコピーでしか無いので、GPUで劇的に速くなることはないでしょう。しかし、意外と負けてない。

sort    
Array size Numpy Cupy
100 0.0061321259 0.6516329956
10000 0.1652002335 0.2315295982
1000000 16.7721819878 3.2199633789
10000000 176.5223050118

30.7312646484

sortとか苦手でしょ?と思ったら意外とcupyやりおる。

eigh    
Array size Numpy Cupy
100 0.1069545746 3.3546682739
10000 7.7713394165 20.8182324219
1000000 799.6927380562 859.2246875

 

Array inv    
Array size Numpy Cupy
100 0.3961801529 1.0362390137
10000 1.05489254 2.6873406982
1000000 215.0195193291 88.9207128906

 

eighは速くはならず、invはちょい速い程度。

というわけで、巨大行列(要素数1000000程度?)の計算にはcupyを使ってみてはいかがでしょうか?