Azure Machine Learning Services の AmlCompute にログインして、複数 GPU を使っているかの確認方法


Azure Machine Learning Service で用意されているサンプルコードでは、NC12, NC24 など1 つの VMに複数のGPUカードが使える場合でも、それを使うサンプルは提供されていません。

サンプルが無いと出来ないのかと思ってしまう事もあるので、ここに纏めておきます。

Azure で用意されている GPU環境:

https://docs.microsoft.com/ja-jp/azure/machine-learning/service/how-to-set-up-training-targets


複数のGPU仮想マシンを使った 分散トレーニングのサンプルも同様です。

https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-train-tensorflow#distributed-training

# アイディア

Azure Machine Learning services は、Pythonベースの Deep Learning のコードの実行に特に制限をかけるものではありません。通常の複数GPUを使う Python のコードを呼べば大丈夫です。

# Azure Machine Learning services の AmlCompute

Azure Machine Learning Compute は、Azure Machine Learning services 管理下のコンピューター環境です。ジョブの起動・停止と連動しての、VM起動・停止。低優先度モードによる60%から最大90%の時間単価の値引きなど幾つものメリットがあります。

ただ、実際の個々の仮想マシンに直接ログインして、例えば複数GPUを使っているかなどの確認手段、Azure のPortal からだけでは出来ません。

ただ、APIである `AmlCompute` クラスには、実は ssh でログインするユーザーアカウントの設定が出来ます。

```python
provisioning_configuration(vm_size='', vm_priority='dedicated', min_nodes=0, max_nodes=None, idle_seconds_before_scaledown=None, admin_username=None, admin_user_password=None, admin_user_ssh_key=None, vnet_resourcegroup_name=None, vnet_name=None, subnet_name=None, tags=None, description=None)
```

https://docs.microsoft.com/ja-jp/python/api/azureml-core/azureml.core.compute.amlcompute(class)?view=azure-ml-py

ここでは、IP Addressの設定は出来ませんが、起動後にアサインされた IP Address は、`list_nodes()` メソッドで取得ができます。

```python
list_nodes()
```

https://docs.microsoft.com/ja-jp/python/api/azureml-core/azureml.core.compute.amlcompute(class)?view=azure-ml-py#list-nodes--

# 複数 GPU を使っているかの検証

ここでは、NC24s_v3 を AmlCompute で作成し、いつもの(?) MNIST にて、複数 GPU を使っているかの確認をします。

Azure Machine Learning Services の利用等は、各種チュートリアルをご参照ください。

 チュートリアル:Azure Machine Learning で MNIST データと scikit-learn を使用して画像の分類モデルをトレーニングする:
https://docs.microsoft.com/ja-jp/azure/machine-learning/service/tutorial-train-models-with-aml


## 1. Keras のトレーニングスクリプトを複数GPUを使えるように変更

サンプルとして、全結合のシンプルなネットワークにしています。ネットワークの構築はお好みで。

Keras + シンプルな TensorFlow実装の場合は、GPU を複数使う場合には `multi_gpu_model`を設定します。

Keras.io ドキュメント:

https://keras.io/ja/getting-started/faq/#how-can-i-run-a-keras-model-on-multiple-gpus

```python
from keras.utils import multi_gpu_model

# Build a simple MLP model
model = Sequential()
# first hidden layer
model.add(Dense(n_h1, activation=activation, input_shape=(n_inputs,)))
model.add(Dropout(dropout))
# second hidden layer
model.add(Dense(n_h2, activation=activation))
model.add(Dropout(dropout))
# output layer
model.add(Dense(n_outputs, activation='softmax'))

model.summary()

# optimize multi_gpu
if gpu > 1:
	model = multi_gpu_model(model, gpus=gpu)
	batch_size = batch_size * gpu

model.compile(loss=loss,
              optimizer=optimizer_types[optimizer](learning_rate),
              metrics=['accuracy'])
```

GPUの数は、外部から貰う事にします。

```python
parser.add_argument('--gpu', type=int, dest='gpu', default=1, help='The count of GPU')

gpu = args.gpu
```

## 2. AmlCompute の作成

以下の通り、NC24_v3を作成します。

```python

from azureml.core.compute import ComputeTarget, AmlCompute
from azureml.core.compute_target import ComputeTargetException

# vm_size list
# https://docs.microsoft.com/en-us/azure/virtual-machines/linux/sizes-gpu
def createamlcompute(gpu_cluster_name, vm_size='Standard_NC6'):

    # Verify that cluster does not exist already
    try:
        compute_target = ComputeTarget(workspace=ws, name=gpu_cluster_name)
        print("Found existing gpu cluster")
    except ComputeTargetException:
        print("Creating new gpucluster")
    
        # Specify the configuration for the new cluster
        compute_config = AmlCompute.provisioning_configuration(vm_size=vm_size, ## Standard_NC6s_v3
                                                           min_nodes=1,
                                                           max_nodes=1,
                                                           admin_username='<Your User Name>',
                                                           admin_user_password='<Your Password>',
                                                           vm_priority='lowpriority') ## vm_priority='lowpriority' | `dedicated'
        # Create the cluster with the specified name and configuration
        compute_target = ComputeTarget.create(ws, gpu_cluster_name, compute_config)

        # Wait for the cluster to complete, show the output log
        compute_target.wait_for_completion(show_output=True)

    print(compute_target.get_status().serialize())
    print(compute_target.list_nodes()) # get IP Address and Port to access from SSH
    return compute_target

#compute_target = createamlcompute('nc6v3','Standard_NC6s_v3')
compute_target = createamlcompute('nc24v3','Standard_NC24s_v3')
```

実行すると、以下の様な構成情報が取得できます。

```python
{'currentNodeCount': 1, 'targetNodeCount': 1, 'nodeStateCounts': {'preparingNodeCount': 0, 'runningNodeCount': 0, 'idleNodeCount': 1, 'unusableNodeCount': 0, 'leavingNodeCount': 0, 'preemptedNodeCount': 0}, 'allocationState': 'Steady', 'allocationStateTransitionTime': '2019-06-03T13:50:21.323000+00:00', 'errors': None, 'creationTime': '2019-06-03T13:48:50.068893+00:00', 'modifiedTime': '2019-06-03T13:49:21.890888+00:00', 'provisioningState': 'Succeeded', 'provisioningStateTransitionTime': None, 'scaleSettings': {'minNodeCount': 1, 'maxNodeCount': 1, 'nodeIdleTimeBeforeScaleDown': ''}, 'vmPriority': 'LowPriority', 'vmSize': 'STANDARD_NC24S_V3'}
[{'nodeId': 'tvmps_b9a52080794b5a5fd4665badfe3d7518b6dbfb7788728cb93aacf1e1247168c1_p', 'ipAddress': '51.143.56.244', 'port': 50000}]
```

`ipAddress`と `port`番号が取得できますね。このサーバーはもう削除しちゃいました😎

## 3. `vmSize` から GPU の数を取得

幸いにて、Azure の NVIDIA GPUマシンは、`vmSize` の最初の数字を `6` で割れば、GPU数と一致します。簡単な正規表現で。

```python
import json
import re

comp_dict = compute_target.get_status().serialize()
vm_size = comp_dict['vmSize']
print(vm_size)

def get_gpu_count(vm_size):
    pattern=r'\d{1,2}'
    s = re.search(pattern, vm_size)
    gpu = vm_size[s.start():s.end()]
    return int(gpu) // 6

gpu = get_gpu_count(vm_size)
print(gpu)
```

以下の様な出力が得られます。

```python
STANDARD_NC24S_V3
4
```

## 4. GPU数を、トレーニング用学習スクリプトへ渡す

どこでやってもいいと思いますが。繰り返しですが。

```python
from azureml.train.dnn import TensorFlow

script_params = {
    '--data-folder': ds.path('mnist').as_mount(),
    '--batch-size': 50,
    '--epoch': 20,
    '--first-layer-neurons': 300,
    '--second-layer-neurons': 100,
    '--learning-rate': 0.001,
    '--activation': 'relu',
    '--optimizer': 'RMSprop',
    '--loss': 'categorical_crossentropy',
    '--dropout': 0.2,
    '--gpu': gpu
}

est = TensorFlow(source_directory=script_folder,
                 script_params=script_params,
                 compute_target=compute_target, 
                 pip_packages=['keras', 'matplotlib'],
                 entry_script='keras_mnist.py', 
                 use_gpu=True)

run = exp.submit(est)
```

## 5. AmlCompute へログイン

各種ログイン情報が得られましたので、ssh でログインします。

```shell
ssh <username>@<ip-address> -p <port-number>
```

## 6. nvidia-smi で確認

Job 実行後に、素早く、nvidia-smi コマンドを実行します。

```bash
$ nvidia-smi
Mon Jun  3 14:18:23 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.40.04    Driver Version: 418.40.04    CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla V100-PCIE...  On   | 00000368:00:00.0 Off |                    0 |
| N/A   31C    P0    34W / 250W |  15552MiB / 16130MiB |     23%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla V100-PCIE...  On   | 0000202B:00:00.0 Off |                    0 |
| N/A   31C    P0    35W / 250W |  15552MiB / 16130MiB |      9%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Tesla V100-PCIE...  On   | 00003EDC:00:00.0 Off |                    0 |
| N/A   30C    P0    35W / 250W |  15552MiB / 16130MiB |      9%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Tesla V100-PCIE...  On   | 0000F431:00:00.0 Off |                    0 |
| N/A   31C    P0    35W / 250W |  15552MiB / 16130MiB |      9%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      9952      C   ...2d55fb33a477d2549fe0ee5ff8fd/bin/python 15541MiB |
|    1      9952      C   ...2d55fb33a477d2549fe0ee5ff8fd/bin/python 15541MiB |
|    2      9952      C   ...2d55fb33a477d2549fe0ee5ff8fd/bin/python 15541MiB |
|    3      9952      C   ...2d55fb33a477d2549fe0ee5ff8fd/bin/python 15541MiB |
+-----------------------------------------------------------------------------+
$
```

という事で、ちゃんと動いていますね。良かったです。