ipyparallel 導入メモ

# やること

互いにssh接続できるマシンをipyparallelでクラスタ化する。
ルート権限はない。
とりあえず１ノードをマスター（jupyter notebook サーバとipyparallelコントローラ）、
全ノードがスレーブ（ipyparallelエンジン）になるように設定する。
作業は全部マスターノード上でok。

# 環境

##### Ubuntu 14.04

* `host1, host2, host3, host4`
* それぞれパスワードなし公開鍵でログイン可能
* /home 以下はNFSをマウント
* 全部ユーザ名は`user`

##### Anaconda

```
$ conda --version
conda 4.4.6
$ which python
/home/user/anaconda3/bin/python
$ python --version
Python 3.6.3 :: Anaconda custom (64-bit)
```

##### jupyter

```
$ jupyter --version
4.3.0
```

##### ipyparallel

```
$ python -c "import ipyparallel; print(ipyparallel.__version__)"
6.0.2
```

# インストール

```
$ conda install ipyparallel
```

# 設定

## Jupyterでの IPython Clusters tab の有効化

```
$ ipcluster nbextension enable --user
```

`--user`をつけないとよくわからないパスのjupyterを探してしまう。


## クラスタ用のprofile, kernelspec の設定

ipyparallelはipythonのprofile単位で設定を保存するが
その設定をjupyter上で切り替えられるように。

##### プロファイル作成

```
$ ipython profile create --parallel --name=parallel
```

##### kernelspec作成

```
$ jupyter kernelspec install --user --name=parallel
```

##### kernelspec確認

```
$ jupyter kernelspec list
Available kernels:
  parallel    /home/user/.local/share/jupyter/kernels/parallel
  python3     /home/user/anaconda3/share/jupyter/kernels/python3
```

もしかしたらkernelspec作成で`--user`いらないかも。

##### 作成したkernelspecでのipython profileを設定

```json:/home/user/.local/share/jupyter/kernels/parallel/kernel.json
{
 "argv": [
  "/home/user/anaconda3/bin/python",
  "-m",
  "ipykernel_launcher",
  "-f",
  "{connection_file}",
  "--profile",
  "parallel"
 ],
 "display_name": "parallel",
 "language": "python"
}
```

`"--profile", "parallel"`を追加した。

## ipyparallelの設定ファイルの編集

```python3:~/.ipython/profile_parallel/ipcluster_config.py
# SSHでエンジン（ノード）を起動
c.IPClusterEngines.engine_launcher_class = 'SSH'
# NFSなのでファイルの同期はしない
c.SSHLauncher.to_fetch = []
c.SSHLauncher.to_send = []
c.SSHEngineSetLauncher.engines = {               
    host:{
        # 各ノードで一つずつカーネルを起動
        'n':1,
　　　　　# パスの設定はログイン時にしかしていないのでここで絶対パスで指定
        'engine_cmd':['/home/user/anaconda3/bin/python',
                          '-m', 'ipyparallel.engine']}
    for host in ['host1', 'host2', 'host3', 'host4']
}
```

```python3:~/.ipython/profile_parallel/ipcontroller_config.py
# 詳しいことはめんどいのでセキュリティガン無視で全部許可
c.RegistrationFactory.ip = '*'
c.HubFactory.client_ip = '*'
c.HubFactory.engine_ip = '*'
c.HubFactory.monitor_ip = '*'
```

# 起動
```
$ ipcluster start --profile=parallel
```
各ノードでエンジンが起動していないときは`--debug`フラグをつけると色々情報が出る。
これで無事起動するならjupyterのipython clustersタブでも起動できるようになる。

# 確認

```python
import ipyparallel as ipp
c = ipp.Client()
print(c.ids)
def test():
    import os, socket
    return os.getcwd(), socket.gethostname()
print(c[:].apply_sync(test))
```
ちゃんと実行できてホスト名が出てくればok。
実行すればわかるけどワーキングディレクトリは同期されないから注意がいる。

# 追記

AsyncResultオブジェクトに対するautocomplete (TAB)はデフォルトだとカーネルが停止する。
次の設定をnotebookの先頭に書いておくと解決した。
詳しいことは時間ある時にでも。。。

```
%config Completer.use_jedi = False
```

# 追記その２

シェルコマンドのバカパラ実行例
https://gist.github.com/emakryo/c00e8484c0844fa646ca9fea8350d2cb

# 追記その３

autoreload が部分的にしか動かない

```python:foo.py
import bar
def foo_func():
    return bar.bar_func()
```

```python:bar.py
def bar_func():
    return 10
```

で`bar_func`をいじって再び実行すると
ローカルでは`foo_func`も`bar_func`も正しく実行されるが
クラスタ上では`bar_func`しか正しく実行されない。
`sync_import`を使った場合でも一緒。

workaroundとしてはipclusterを再起動すればとりあえず動きはするが
いちいち再起動するのはめんどくさい。

# 追記その４

モジュールのリロードは`importlib.reload`を使えばできる。

```python3
def reload(mod_names):
    import sys
    from importlib import reload
    for m in mod_names:
        if m in sys.modules:
            reload(sys.modules[m])

c = ipp.Client()
c[:].apply_sync(reload, ['my_module'])
```



# 参照

* http://ipyparallel.readthedocs.io/en/stable/process.html (ipyparallel設定)
* http://jupyter.readthedocs.io/en/latest/migrating.html (kernelspecとprofileについて)
* https://qiita.com/tomochiii/items/8b937f15c79a0c3eae0e (kernelspec)