Win10のAnaconda環境にclpyをmingwビルドで入れた話
記事タイトル間違ってません
clpy*1というcupyをベースに?バックエンドをOpenCLにしたライブラリが出ていたのでさっそくMinGWでビルドしてみた。
環境
前回と一緒
- Windows10 pro 64bit
- Anaconda3 5.1.0
- pip 9.0.1
- MinGW w64 7.3.0
- clpy 2.1.0a0
clpyはGitHubからclone、もしくはzipダウンロードしておく。
前準備
- g++にパスを通しておく
- README.mdの通りに環境変数を設定する
CLPY_GENERATE_CUPY_ALIASを設定しておくとclpyと書くところをcupyと書くことができてcupyを使っているような感じでコーディングできるっぽい。
以降は魔改造した内容
distutils
- Anaconda3\Lib\distutils\ccompiler.py の改造
ccompilerを直接使っているせいなのか、distutils.cfgを見てくれない。ので、無理やりmingw32にする。
_default_compilers = ( # Platform string mappings # on a cygwin built python we can use gcc like an ordinary UNIXish # compiler ('cygwin.*', 'unix'), # OS name mappings ('posix', 'unix'), + ('nt', 'mingw32'), - ('nt', 'msvc'), )
- Anaconda3\Lib\distutils\cygwinccompiler.py の改造
sys.versionを見てVisual C++ ライブラリを決める箇所をMinGW向けにバージョン変更する
def get_msvcr(): """Include the appropriate MSVC runtime library if Python was built with MSVC 7.0 or later. """ msc_pos = sys.version.find('MSC v.') if msc_pos != -1: msc_ver = sys.version[msc_pos+6:msc_pos+10] if msc_ver == '1300': # MSVC 7.0 return ['msvcr70'] elif msc_ver == '1310': # MSVC 7.1 return ['msvcr71'] elif msc_ver == '1400': # VS2005 / MSVC 8.0 return ['msvcr80'] elif msc_ver == '1500': # VS2008 / MSVC 9.0 return ['msvcr90'] elif msc_ver == '1600': # VS2010 / MSVC 10.0 return ['msvcr100'] elif int(msc_ver) >= 1900: # VS2015 / MSVC 14.0 + return ['msvcr100'] - return ['msvcr140'] else: raise ValueError("Unknown MS Compiler version %s " % msc_ver)
clpyのinstall\build.py変更
g++には「MANIFEST」というコマンドライン引数は無いので、build_shlib()のpostargsを空っぽの配列に変更する
try: + postargs = [] - postargs = ['/MANIFEST'] if sys.platform == 'win32' else [] compiler.link_shared_lib(objects, os.path.join(temp_dir, 'a'),
いつものcmath改造
mingw\lib\gcc\x86_64-w64-mingw32\7.3.0\include\c++\cmathの「hypot」でコンパイルエラーになるのでコメントアウトする。
- using ::hypot; +// using ::hypot;
インストール
README.mdに書かれている通り、以下で大丈夫と思う。自分はビルド確認をやっていたのでbuildしてからinstallした。
$ python setup.py install
動作確認
pyopenclのハンズオン*3を見ながら書いていたコードをさらに改造して、行列積の結果をclpyと比較してみる。CLPY_GENERATE_CUPY_ALIASを設定してビルドしたので以下の通り「cupy」と書いてclpyを使える。
import pyopencl as cl from pyopencl import mem_flags import cupy import numpy as np import time input_size = 1024 context = cl.create_some_context() queue = cl.CommandQueue(context) # inputs lhs_host = np.random.randn(input_size, input_size).astype(np.float32) rhs_host = np.random.randn(input_size, input_size).astype(np.float32) lhs_cl = cupy.asarray(lhs_host) rhs_cl = cupy.asarray(rhs_host) lhs_dev = cl.Buffer(context, mem_flags.READ_ONLY|mem_flags.COPY_HOST_PTR, hostbuf=lhs_host) rhs_dev = cl.Buffer(context, mem_flags.READ_ONLY|mem_flags.COPY_HOST_PTR, hostbuf=rhs_host) # outputs result_host_pycl = np.empty((input_size, input_size), np.float32) result_dev = cl.Buffer(context, mem_flags.WRITE_ONLY, result_host_pycl.nbytes) result_cl = cupy.array(result_host_pycl) # 暫定コード。emptyなcupy.ndarrayを作る方法が不明。 # pyopenclの行列積kernel(最適化なし) program = cl.Program(context, ''' __kernel void mul( __global const float *a, __global const float *b, __global float *c, const int n ) { const int i = get_global_id(0); const int j = get_global_id(1); const int index = j*n + i; float accm = 0; for (int k=0;k<n;k++) { accm += a[j*n + k] * b[k*n + i]; } c[index] = accm; } ''').build() n = np.int32(input_size) start = time.time() # cupy行列積 result_cl = cupy.dot(lhs_cl, rhs_cl) # pyopencl行列積 ev = program.mul(queue, lhs_host.shape, None, lhs_dev, rhs_dev, result_dev, n) ev.wait() stop = time.time() # result_host_pycl <- result_dev cl.enqueue_copy(queue, result_host_pycl, result_dev) result_host_cupy = cupy.asnumpy(result_cl) # 差分の絶対値から最大(差分が最大)のものを表示 print('clpy vs pyopencl diff(max):', np.max(np.abs(result_host_cupy - result_host_pycl))) print(np.all(result_host_cupy == result_host_pycl)) # 全部同値ならTrueのつもり print('CPU vs clpy diff(max):', np.max(np.abs(np.dot(lhs_host, rhs_host) - result_host_cupy))) print(np.all(np.dot(lhs_host, rhs_host) == result_host_cupy)) print('exec time:', stop - start, 'sec')
実行結果はこんな感じ。GPU側はfmaが使われてCPUと結果が一致しないと思われる。
>python clpy-test.py
Choose platform:
[0]
Choice [0]:0
Choose device(s):
[0]
[1]
Choice, comma-separated [0]:1
Set the environment variable PYOPENCL_CTX='0:1' to avoid being asked again.
clpy vs pyopencl diff(max): 0.0
True
CPU vs clpy diff(max): 0.00021362305
False
exec time: 0.37632179260253906 sec
*1:https://github.com/fixstars/clpy
*2:OpenCLのSDKを入れてない場合は「C:\Windows\System32」を入れておけばOpenCL.dllとリンクしてくれる
*3:http://pykyoto201109-pyopencl.s3-website-ap-northeast-1.amazonaws.com/pyopencl.html