CuPyを使ってみよう
https://gyazo.com/e34f5f5c04893f8d28cd9a222a540175
CuPy について
CuPy はディープラーニングでよく使用される Chainer を開発したPreferred Networks社が主導して開発しているオープンソースのPython ライブラリです。
CUDA対応GPUをPythonから利用できる
NumPy とほとんど同じAPIを提供
デバッグも非常に簡単
つまり、CuDA + NumPy = Cupy ということです。
多くのNumPyやSciPy関数は、元の関数とほぼ同じインターフェイスでCuPyに実装されています。
CuPyには独自のJITコンパイラは付属していません。 ただし、JITコンパイラを提供するNumbaとの親和性は比較的良好です。 CuPyに実装されていない関数を必要とする場合は、Numbaあるいは PyCUDAを利用してカスタムカーネルを作成することになります。
インストール
GoogleColab ではデフォルトで利用することができます。
他のプラットフォームで cupy をインストールする場合は、次のように行います。
code: bash
$ pip install -U cupy
CUDAバージョンを合わせる必要がある場合は、次のようにパッケージ名変更します。
table: cupyのインストール
CUDAバージョン インストールコマンド
v9.0 pip install cupy-cuda90
v9.2 pip install cupy-cuda92
v10.0 pip install cupy-cuda100
v10.1 pip install cupy-cuda101
v10.2 pip install cupy-cuda102
v11.0 pip install cupy-cuda110
v11.1 pip install cupy-cuda111
CuPy の使用方法
Numpyと同様に、CuPyにもndarrayクラスcupy.ndarrayがあり、
基本的には NumPy の関数名でそのまま利用することができます。
code: python
上記の例のx_gpuは、cupy.ndarrayのインスタンスです。 numpyがcupyに置き換えられていることを除けば、NumPyのものと同じものが作成されていることがわかります。
code: python
import cupy as cp
import numpy as np
l2_cpu = np.linalg.norm(x_cpu)
l2_gpu = cp.linalg.norm(x_gpu)
print("Using Numpy: ", l2_cpu)
print("\nUsing Cupy: ", l2_gpu)
https://gyazo.com/2cfd19ab6af0e1db0f5d803c7cb322db
code: python
ary = cp.arange(10).reshape((2,5))
print(repr(ary))
print(ary.dtype)
print(ary.shape)
print(ary.strides)
https://gyazo.com/b5e9b11c35d1cad7e5779bf0dbdf1471
この配列は、デフォルトのGPU(デバイス0)のGPUメモリにあります。 これは、特別なデバイス属性を調べることで確認できます。
code: python
ary.device
https://gyazo.com/0e5c44fbf4bc645e57b108f4f85528df
cp.asarray()関数を使用して、CPUからGPUにデータを移動できます。
code: python
ary_cpu = np.arange(10)
ary_gpu = cp.asarray(ary_cpu)
print('cpu:', ary_cpu)
print('gpu:', ary_gpu)
print(ary_gpu.device)
https://gyazo.com/98bc3997bb79b00a0d3da9d9004fbd8e
GPU配列の内容を出力するとき、CuPyはデータをGPUからCPUにコピーして戻し、結果を出力できるようにすることに注意してください。
GPU上のデータが完成したら、cp.asnumpy()関数を使用してCPU上のNumPy配列に変換し直すことができます。
code: python
ary_cpu_returned = cp.asnumpy(ary_gpu)
print(repr(ary_cpu_returned))
print(type(ary_cpu_returned))
https://gyazo.com/e916eea106d0f51f235e297b73ab16ca
GPU Array Math
ほとんどのNumPyメソッドは、同じ関数名と引数を持つCuPyでサポートされています。
code: python
print(ary_gpu * 2)
print(cp.exp(-0.5 * ary_gpu**2))
print(cp.linalg.norm(ary_gpu))
print(cp.random.normal(loc=5, scale=2.0, size=10))
https://gyazo.com/6b4a592b28c6d365a4cb55c92e2acfb5
これらの機能を初めて実行すると、わずかな一時停止が発生する場合があります。 これは、CuPyがCUDA関数をその場でコンパイルし、将来再利用するためにディスクにキャッシュする必要があるためです。
Numbaとの連携
NumPyと同様に、Numbaは次の場合にCuPyで使用すると便利です。
複数の操作を組み合わせて効率を高めたい
CuPy関数を組み合わせて簡単に説明できないカスタムアルゴリズムを実装したい
Python関数をマシンコードに変換するためのNumbaのコンパイラパイプラインを使用して、スタンドアロンまたはCuPyで使用できるCUDA関数を生成したい
Numbaでサポートされている基本的なアプローチは2つあります。
ufuncs / gufuncs
CUDA Pythonカーネル
code: python
import cupy as cp
import numpy as np
import time
# NumPy and CPU Runtime
s = time.time()
x_cpu = np.ones((1000, 1000, 100))
e = time.time()
print("Time consumed by numpy: ", e - s)
# CuPy and GPU Runtime
s = time.time()
x_gpu = cp.ones((1000, 1000, 100))
e = time.time()
print("\nTime consumed by cupy: "e - s)
https://gyazo.com/4ec7f256f705cfcfa960690e51b9f019
ここでは、CuPyがNumPyよりもはるかに高速に動作する可能性があることがわかります。
CPU/GPUのどちらでも動作する関数
cupy.get_array_module()を呼び出すと、cupy.ndarrayオブジェクトが引数にあるかどうかで、cupy/numpyのいずれかの関数を返してくれます。
code: python
def logsumexp(x, axis=None):
xp = cuda.get_array_module(x)
x_map = x.map(x, axis=axis)
return x_map + xp.log(xp.exp(x - x_max).sum(axis=axis))
CuPyは、cupy.ndarrayオブジェクトに多くの関数を実装します。 詳細はCuPy がサポートするNumPy APIのドキュメント を参照してください。 つまり、CuPyの機能を十分に利用するためには、NumPyの知識が重要になります。したがって、NumPyのドキュメントを読んでおくことをお勧めします。 まとめ
CuPy はほぼNumPy の代わりに使用することができます。違いはNumPyがCPUでの計算に対して、CuPy が GPU で計算することです。
データはCPU(ホスト)からGPU(デバイス)にコピーされ、そこで計算されます。 計算後、numpyなどが操作できるようにCPUにコピーして戻す必要があります。
CuPyでGPUスピードアップされた関数は、少なくとも4つの点で最適化されています。
入力サイズ
計算の複雑さ
CPU / GPUコピー
データ型
具体的には、入力サイズが小さすぎる、計算が単純すぎる、GPU / CPUとの間でデータが過剰にコピーされる、入力タイプが大きすぎるような場合、GPUスピードアップ関数が遅くなる可能性があります。
補足説明
CuPyはChainnerのとともに開発が進んで来ました。そのChainnerは2019年12月に
開発が終了しています。
「Preferred Networks、深層学習の研究開発基盤をPyTorchに移行」
しかし、CuPyについては開発/保守が継続されることになっています。
参考