[db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by...
-
Upload
insight-technology-inc -
Category
Technology
-
view
373 -
download
3
Transcript of [db analytics showcase Sapporo 2017] A15: Pythonでの分散処理再入門 by...
Python 分散処理再入門DB Analytics showcase Sapport 2017
(株)HPCソリューションズ 飯坂剛一
今日の話題
• Data処理について • Python での分散処理
– threading / multiprocessing– joblib– dask/distributed– mpi4py
今日の話題にしないこと
• Python の文法とか• モジュールやフレームワークの詳細
なんとなく判ったつもりでOK
About me
• 飯坂 剛一 (いいさか ごういち)– (株) HPCソリューションズ– [email protected] / [email protected]– 普段は...役務やコンサルタントをしています。
• クラスタシステムの導入や構築• Deep Learning プラットフォームの導入や構築• Open Source の導入と活用方法のコンサルティング
– 好きな言語: Python
Data処理
たとえばDeepLearningでのデータ処理
• 画像の数が少なければ良い結果が得られない– 多数のデータを収集
• 画像のクラス分類が重要– 対応する画像だけに分類/変換/加工
MNIST データ・セット
• 国立標準技術研究所の混合データ セット
• 合計70,000 枚の画像– 学習用60,000 枚– 判別用10,000 枚
• 28x28ピクセル
CIFAR-10 データ・セット
• 10カテゴリのデータ・セット• 合計60,00枚のカラー画像
– 学習用50000枚• 各クラス5,000枚
– 判別用10000枚• 各クラス1,000枚
• 32x32ピクセル• PythonのcPickle形式
Google Cat
• コンピュータが「猫」がどういうものかを教示されることなく、自力で「猫」を認識できた – 強く反応するニューロンが生まれた
• YouTube にある動画から200x200ピクセルの画像を1000万枚取得
• 1000ノードで3日かけて学習https://googleblog.blogspot.jp/2012/06/using-large-scale-brain-simulations-for.html
為替取引: AlpacaAlgo
為替取引でのデータ
• 通貨ペアごとのデータを保持– USDJPY, EURUSD, EURJPY, GBPUSD, GBPJPY...
• 各時間単位での売値(Bit), 買値(Ask)の時系列データ– 1M, 1D, 4H, 1H, 30M, 15M, 5M, 1M, Tick
• 各時間単位での売買ボリュームの時系列データ
Python
• 低い学習コストと高い可読性• 豊富なライブラリやフレームワーク、アプリケーション
– SciPy, NumPy, Pandas, DASK, Matplotlib, Seaborn, Bokeh– Chainer, Tensorflow, Caffe, Orange3
• データ処理やDeepLearningで楽ができる
どのPythonがいいの?• Anaconda Python をお勧めします
– https://www.continuum.io/downloads
Orange3: ワークフローツール
Python / Jupyter notebook のカバー領域
探索と分析プロジェクトでnotebookのようなアプリやPythonを活用するコラボレーションと公開共有ノートブックやプロジェクトの集中管理、エンタープライズ認証による安全な管理
ディプロイと運用並列フレームワークとリソース管理の統合
アナリスト データサイエンティスト 開発者 データエンジニア DevOps
https://www.continuum.io/anaconda-enterprise-notebooks
Conda
• Python 3.6 が使える (Python 2.x, Python 3.x)• x86_64, PowerPC, ARMv7 サポート
– Raspbery Pi2 でも動作する
Anaconda と Miniconda
• Miniconda は Anaconda のサブセット• 後からパッケージは追加できるので Miniconda でOK
– 異なるバージョンの Python も追加できる• 2つのバージョン
– Anaconda2 / Miniconda2 - Python 2.7– Anaconda3 / Miniconda3 - Python 3.6
• Python 2.7 は 2020/01/01 でサポート終了– https://www.python.org/dev/peps/pep-0373/
Python での分散処理
SMP と Cluster
• SMP (Symmetric Multiprocessing)– メモリ共有型並列コンピューティング– 現在はほぼすべてのサーバ、PC、スマートフォンがSMP– 処理はひとつのノード内だけで実行される
• Cluster– 複数のコンピュータをネットワークで結合した並列コンピューティング– 冗長化クラスタ(HAクラスタ)と区別するために
BeoWulf型クラスタやHPCクラスタと呼ばれることもある– 処理は複数のノードで実行される
スケールアップとスケールアウト
https://www.idcf.jp/words/scaleup.html
スケールアップとスケールアウト
• スケールアップ– 実行させるH/Wを更新することになる– 単一ノード内での並列処理– スレッド
• スケールアウト– H/Wは追加するだけでよい– プログラムをマルチノード対応化が必要– ネットワーク性能も重要になる– マルチプロセス
プロセスとスレッド• 各プロセスのメモリは独立• スレッドはプロセスのメモリを共有
https://web.kudpc.kyoto-u.ac.jp/manual/en/parallel
Pythonとスレッド
PythonでのスレッドとGIL
http://www.tivix.com/blog/lets-go-python/
GIL問題の回避方法:その1
• コードを非同期処理で書いてみる (もし可能なら)• 主なモジュール
– gevent– tornado– asyncio
非同期処理が向く問題
gevent の例: イベントループ
tornado の例: コールバック
gevent と tornado の比較
• グリーンスレッド– スレッドはハードウェアではなくアプリケーションレベルで制御される– スレッドのようにみえるが、CPUのコンテキストスイッチは起きない– 通常のスレッドプログラミングの問題点はのこる
• コールバック– スレッドプログラミングとは同じようには書けない– スレッド/コルーチンはプラグラマには隠蔽される– 例外は飲み込まれて伝わってこない– コールバックの結果を集められない
asyncio の例: (Python 3.5以降のasync/await)
asyncio async/await
• CPUコンテキストのスイッチングは起きない– イベントループを使用しアプリケーションレベルでコルーチンを切り替える
• 競合が発生しない– asyncio は1度に1つのコルーチンだけを実行する
• デッドロックにならない– 競合が発生しないのでロックを使用する必要がなく安心
• リソースが欠乏することがない– コルーチンはスレッドで実行されるので余分なソケットやメモリが不要
コンテキストスイッチの発生する状況
http://ossforum.jp/node/752
GIL問題の回避方法:その2
• マルチプロセスで並列処理をすればよい– ロックを使ってしまうと状況はかわらないことに注意
• 主なモジュール– multiprocessing– joblib– DASK/distributed– mpi4py
マルチプロセスでの注意点
• プロセスごとに別々のメモリ領域で動作しているということ– 変数へのアクセスには注意が必要– データ/オブジェクトのやり取りにコツがいる
• プロセス生成とデータ/オブジェクトのコピーのオーバーヘッドがある– やみくもに並列化をせずに、もっとも効果のある部分を並列化– 20/80セオリー:全体の20%の処理が、80%の時間を消費している
• 物理コア数以上のプロセスで実行すると速度向上は期待できない– 並列度が外部に依存していることに注意する
multiprocessing モジュール
• スレッドの替りにプロセスを生成してGILを回避– ローカル(SMP)とリモート(Cluster)の両方で並列処理ができる
• threading モジュールと同様のAPI(よく似ている)– start(), run(), join()...
joblib モジュール
• タスク出力のディスクキャッシュを再遅延評価• 簡単でシンプルな並列化処理• 実行処理のトラッキングとロギング• 効率的な配列バッファ処理のため大きなnumpy配列が高速• zlibでデータ圧縮してのオブジェクトをpickle/非pickle化
– オブジェクト階層をバイトストリームへの変換と復元
他にもいろいろな方法がある
• Python + Spark• Python + Hadoop• Python + RabbitMQ• Python + Elasticsearch
jupyter notebook の方がわかりやすいので...
DASK dashboard
DASK
• コレクション、グラフ、スケジューラで構成• スケールアップとスケールアウト• Out-of-Core 処理
コレクション
• Array - NumPy の array と同じインタフェース• DataFrame - Pandas の DataFrame と同じインタフェース• Bag - Python オブジェクトを格納• Delayed - 遅延評価
Array
import numpy as npf = h5py.File('myfile.hdf5')x = np.array(f['/small-data'])x - x.mean(axis=1)
import dask.array as daf = h5py.File('myfile.hdf5')x = da.from_array(f['/big-data'], chunks=(1000, 1000)) x - x.mean(axis=1).compute()
• NumPyと同じように使える
DataFrame
import pandas as pd dd.read_csv('2015-*-*.csv')df.groupby(df.user_id).value.mean()
import dask.dataframe as dddf = dd.read_csv('2015-*-*.csv')df.groupby(df.user_id).value.mean().compute()
• Pandas と同じように使える
グラフ
スケジューリング
• SMPとクラスタの両方をサポート– 単一ノードでのスケールアップ– クラスタでのスケールアウト
クラスタの作成
• 準備: 各ノードで distibuted のインストール$ conda create -y -n demo$ source activate demo(demo) $ conda install bokeh(demo) $ conda install distributed
スケジューラの起動
(demo) $ dask-schudler dask-schedulerdistributed.scheduler - INFO - -----------------------------------------------distributed.scheduler - INFO - Scheduler at: 159.203.166.31:8786distributed.scheduler - INFO - http at: 159.203.166.31:9786distributed.scheduler - INFO - bokeh at: 159.203.166.31:8788distributed.bokeh.application - INFO - Web UI: http://159.203.166.31:8787/status/distributed.scheduler - INFO - -----------------------------------------------distributed.scheduler - INFO - Receive client connection: 20d2354a-dd86-11e6-9fe3-a634b264672b
ワーカーの起動
• スケジューラの"ホスト:ポート"を引数として与える(demo) $ dask-worker 159.203.166.31:8786 --memory-limit=autodistributed.nanny - INFO - Start Nanny at: 138.197.11.22:36452distributed.worker - INFO - Start worker at: 138.197.11.22:39702distributed.worker - INFO - nanny at: 138.197.11.22:36452distributed.worker - INFO - http at: 138.197.11.22:34241distributed.worker - INFO - bokeh at: 138.197.11.22:8789distributed.worker - INFO - Waiting to connect to: 159.203.166.31:8786distributed.worker - INFO - -------------------------------------------------distributed.worker - INFO - Threads: 2distributed.worker - INFO - Memory: 2.49 GBdistributed.worker - INFO - Local Directory: /tmp/nanny-xk59hltvdistributed.worker - INFO - -------------------------------------------------
クラスタの作成 (共有ファイルシステムを使う場合)
• スケジューラの起動
• ワーカーの起動
$ dask-scheduler --scheduler-file /path/to/scheduler.json
$ dask-worker --scheduler-file /path/to/scheduler.json
クラスタの作成 (Python API)
• プログラムの中からスケジューラを起動from distributed import Schedulerfrom tornado.ioloop import IOLoopfrom threading import Thread
loop = IOLoop.current()t = Thread(target=loop.start, daemon=True)t.start()
s = Scheduler(loop=loop)s.start('tcp://:8786')
クラスタの作成 (Python API)
• プログラムの中からワーカーを起動from distributed import Workerfrom tornado.ioloop import IOLoopfrom threading import Thread
loop = IOLoop.current()t = Thread(target=loop.start, daemon=True)t.start()
w = Worker('tcp://159.203.166.31:8786', loop=loop)w.start()
タスクの実行
• スケジューラの"ホスト:ポート"を与える
from dask.distributed import Clientclient = Client('159.203.166.31:8786')
data = [client.submit(load, fn) for fn in filenames]processed = [client.submit(process, d, resources={'GPU': 1}) for d in data]final = client.submit(aggregate, processed, resources={'MEMORY': 70e9})
Ansible で実行環境を構築すると楽
• プレイブックで簡単にスケールアウト
jupyter notebook の方がわかりやすいので...
Blaze
• 異なるストレージシステムを抽象化してくれる– http://blaze.pydata.org/en/latest
odo
• 簡単にデータ変換してくれる– http://odo.pydata.org/en/latest/
MPI(Message Passing Interface)
• 並列処理をするための標準化された規格
MPI
• MPI_ではじまる関数や定数• MPI_Init() と MPI_Finalize() に囲む
– この中でMPI関数を呼び出させる• MPIプログラムで生成される各プロセスはランクという値が設定される
MPI: Hello World in C
mpi4py: Hello World in Python
#!/usr/bin/env python
from mpi4py import MPIimport sys
size = MPI.COMM_WORLD.Get_size() # SPMD環境の規模を取得rank = MPI.COMM_WORLD.Get_rank() # このプロセスのランク(番号)name = MPI.Get_processor_name() # このプロセスのプロセス名
sys.stdout.write( "Hello, World! I am process %d of %d on %s.\n" % (rank, size, name))
Hello World 実行方法
$ mpicc -o helloworld.exe helloworld.c$ mpiexec -n 4 helloworld.exe
プロセス数の指定
$ mpiexec -n 4 python helloworld.py
conda を使うときは...
• システム側の python と混乱しやすい• module コマンドで環境をロードするようにするとよい
– http://modules.sourceforge.net/– 例: module load conda
• conda 環境を作っていればアクティベートを忘れやすい– 例: source activate demo
mpi4py: HelloWorld - broadcast
mpi4py: HelloWorld - P2P (Send/Recv)
mpi4py: 集計 - P2P (Send/Recv)
Send/Recvの動作イメージ
https://www.nesi.org.nz/sites/default/files/mpi-in-python.pdf
mpi4py: 集計 - reduce
mpi4py: reduce の動作イメージ
https://www.nesi.org.nz/sites/default/files/mpi-in-python.pdf
画像処理: ノイズ除去シリアル版import numpy as npfrom skimage import data, img_as_floatfrom skimage.filter import denoise_bilateralimport skimage.ioimport os.pathimport time
curPath = os.path.abspath(os.path.curdir)noisyDir = os.path.join(curPath,'noisy')denoisedDir = os.path.join(curPath,'denoised')
画像処理: ノイズ除去シリアル版 (続き)def loop(imgFiles): for f in imgFiles: img = img_as_float(data.load(os.path.join(noisyDir,f))) startTime = time.time() img = denoise_bilateral(img, sigma_range=0.1, sigma_spatial=3), skimage.io.imsave(os.path.join(denoisedDir,f), img) print("Took %f seconds for %s" %(time.time() - startTime, f))
def serial(): total_start_time = time.time() imgFiles = ["%.4d.jpg"%x for x in range(1,101)] loop(imgFiles) print("Total time %f seconds" %(time.time() - total_start_time))
if __name__=='__main__': serial()
画像処理: ノイズ除去MPI版
画像処理: ノイズ除去MPI版 (続き)def loop(imgFiles,rank): for f in imgFiles: img = img_as_float(data.load(os.path.join(noisyDir,f))) startTime = time.time() img = denoise_bilateral(img, sigma_range=0.1, sigma_spatial=3), skimage.io.imsave(os.path.join(denoisedDir,f), img) print ("Process %d: Took %f seconds for %s" % ( rank, time.time() - startTime, f))
画像処理: ノイズ除去MPI版 (続き)
jupyter notebook の方がわかりやすいので...
GPGPU
GPGPUで使われるコンポーネント
• NVIDIA CUDA– CuPY (Chainer)– PyCUDA– pygpu
• cuDNN - DeepLearning ライブラリ• NCCL - マルチGPUを効率的に処理させる• CUDA-aware MPI
CUDA-aware MPI
• 対応するオープンソースのMPI実装– MVAPICH2 1.8以降– OpenMPI 1.7以降
通常のMPIでのデータ送受信
• ホスト上のメモリへのポインタのみがMPIに渡される。 • cudaMemcpyを使用してGPUバッファをホストメモリ経由で
ステージングする必要がある。//MPI rank 0cudaMemcpy(s_buf_h,s_buf_d,size,cudaMemcpyDeviceToHost);MPI_Send(s_buf_h,size,MPI_CHAR,1,100,MPI_COMM_WORLD);
//MPI rank 1MPI_Recv(r_buf_h,size,MPI_CHAR,0,100,MPI_COMM_WORLD, &status);cudaMemcpy(r_buf_d,r_buf_h,size,cudaMemcpyHostToDevice);
CuDA-aware MPIでのデータ送受信
• CuDA-aware MPIでは cudaMemcpy() が不要。
//MPI rank 0MPI_Send(s_buf_d,size,MPI_CHAR,1,100,MPI_COMM_WORLD);
//MPI rank n-1MPI_Recv(r_buf_d,size,MPI_CHAR,0,100,MPI_COMM_WORLD, &status);
性能比較
https://devblogs.nvidia.com/parallelforall/benchmarking-cuda-aware-mpi/
UVA(Unified Virtual Addressing)
• GPUとホストのメモリを単一仮想アドレス空間に統合
GPUDirect RDMA
• GPUのメモリ上のデータをホストのメモリを介さずにNICに直接送信
GPUDirect P2P
• 同じノード内の2つのGPUのメモリ間でバッファを直接コピーする
通常のMPIでのデータの流れ
GPUメモリ GPUメモリ
Host Buffer Host Buffer
• 2つのGPUのメモリ間に複数のバッファがあり、それぞれコピーする
通常のMPIでのデータの流れ• それぞれのバッファでのコピーだけ時間がかかる
CUDA-aware MPI / GPUDirect P2P
GPUメモリ GPUメモリ
Host Buffer Host Buffer
• 同じシステム内の2つのGPUのメモリ間では直接コピーする
CUDA-aware MPI / GPUDirect RDMA
GPUメモリ GPUメモリ
Host Buffer Host Buffer
• 他ノードのGPUのメモリ間ではホストのメモリを経由せずにコピー
CUDA-aware MPIでのデータの流れ
• バッファへのメモリ転送が不要なので処理速度が向上する
おまけ
HPCソリューションズ HPCS-DLD
HPCS-DLD
• Anaconda Python をベースにDeepLearningのフレームワークやライブラリ、ミドルウェアをパッケージングしたソフトウェアディストリビューション
• それぞれのオープンソース・ライセンスに準じて無償で使用することができます。
– http://www.hpc-sol.co.jp/dld/ • サブスクリプション契約をするとアップディトレポジトリへのアクセスと
アップディトリクエストが可能になります。
参考資料• Anaconda Python
– https://www.continuum.io/• NumPy
– http://www.numpy.org/• SciPy
– https://www.scipy.org/• Blaze
– http://blaze.pydata.org/• Statsmodules
– http://www.statsmodels.org/stable/index.html• Pandas
– http://pandas.pydata.org/• Blaze
– http://blaze.pydata.org/• HDF5
– https://support.hdfgroup.org/HDF5/
• matplotlib– https://matplotlib.org/
• seaborn– https://seaborn.pydata.org/
• Jupyter– http://jupyter.org/
• Orange– https://orange.biolab.si/
• dispy– http://dispy.sourceforge.net/
• Parallel Python– http://www.parallelpython.com/
• mpi4py– http://pythonhosted.org/mpi4py/
• OpenMPI– https://www.open-mpi.org/
参考資料• Calculations with arrays bigger than your memory (Dask arrays)
– http://earthpy.org/dask.html• mpi4py-examplpes
– https://github.com/jbornschein/mpi4py-examples• Grok The GIL: How to write fast and thread-safe Python
– https://opensource.com/article/17/4/grok-gil• Python 3.5 で実装された async/await を使って軽量スレッドの性能ベンチマーク
– http://qiita.com/haminiku/items/0aaf87e9a52ed41b60a7• Python における非同期処理: asyncio逆引きリファレンス
– http://qiita.com/icoxfog417/items/07cbf5110ca82629aca0• Asynchronous Python
– https://hackernoon.com/asynchronous-python-45df84b82434• THe Open Source Data Science Masters
– http://datasciencemasters.org/• Distributed parallel programming in Python : MPI4PY
– https://www.howtoforge.com/tutorial/distributed-parallel-programming-python-mpi4py/
• Awesome Python– https://awesome-python.com/