DatabricksにおけるHorovodRunnerの活用：Horovodによる分散ディープラーニング

[HorovodRunner: distributed deep learning with Horovod \| Databricks on AWS](https://docs.databricks.com/applications/machine-learning/train-model/distributed-training/horovod-runner.html) [2022/8/17時点]の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

HorovodRunnerは、[Horovod](https://github.com/horovod/horovod)フレームワークを用いたDatabricks上で分散ディープラーニングワークロードを実行するための汎用APIです。HorovodとSparkのバリアーモードを組み合わせることで、DatabricksのSparkで長時間稼働するディープラーニングトレーニングジョブに対して高い安定性を提供できるようになります。HorovodRunnerはディープラーニングトレーニングのコードとHorovodのフックを含むPythonメソッドを受け取ります。HorovodRunnerはそのメソッドをドライバー上でpickleし、Sparkワーカーに分散させます。HorovodのMPIジョブは、バリアー実行モードを用いてSparkジョブとして埋め込まれます。最初のエグゼキューターが`BarrierTaskContext`を用いて全てのタスクエグゼキューターのIPアドレスを収集し、`mpirun`を用いてHorovodのジョブを起動します。それぞれのPython MPIプロセスは、pickleされたユーザープログラムをロードし、デシリアライズして実行します。
![](https://docs.databricks.com/_images/horovod-runner.png)

# HorovodRunnerによる分散トレーニング

HorovodRunnerを用いることで、HorovodトレーニングジョブをSparkジョブとして起動することができます。HorovodRunnerのAPIは以下のテーブルのメソッドをサポートしています。詳細は、[HorovodRunner API documentation](https://databricks.github.io/spark-deep-learning/#sparkdl.HorovodRunner)をご覧ください。

| メソッドとシグネチャ | 説明 |
|:--|:--|
| **`init(self, np)`**  | HorovodRunnerのインスタンスを作成します。  |
| **`run(self, main, **kwargs)`**  |  `main(**kwargs)`を呼び出すことでHorovodトレーニングジョブを実行します。cloudpickleを用いてmain関数とキーワードの引数はシリアライズされ、クラスターのワーカーに分散されます。 |

HorovodRunnerを用いた分散トレーニングプログラムを開発する一般的なアプローチは以下の通りとなります。

1. ノードの数を用いて初期化された`HorovodRunner`インスタンスを作成します。
1. [Horovod usage](https://github.com/horovod/horovod#usage)で説明されている方法に従って、Horovodのトレーニングメソッドを定義し、メソッド内にimport文を追加するようにします。
1. `HorovodRunner`インスタンスにトレーニングメソッドを引き渡します。

例は以下のようになります。

```py:Python
hr = HorovodRunner(np=2)

def train():
  import tensorflow as tf
  hvd.init()

hr.run(train)
```

`n`個のサブプロセスを持つドライバーでのみHorovodRunnerを実行するには、`hr = HorovodRunner(np=-n)`を使用します。例えば、ドライバーのーどに4GPUがある場合、最大`4`まで`n`を指定することができます。パラメーター`np`の詳細については、[HorovodRunner API documentation](https://databricks.github.io/spark-deep-learning/#api-documentation)をご覧ください。サブプロセスに一つのGPUを固定する方法に関しては、[Horovod usage guide](https://github.com/horovod/horovod#usage)をご覧ください。

よくあるエラーは、TensorFlowオブジェクトが見つからない、pickleされないというものです。これは、ライブラリのimport文が他のエグゼキューターに分散されていない場合に生じます。この問題を回避するには、全てのimport文(例えば、`import tensorflow as tf`)をHorovodのトレーニングメソッドと、Horovodトレーニングメソッドで呼び出されるその他の全てのユーザー定義関数の*両方*に含めるようにしてください。

# Horovod TimelineによるHorovodトレーニングの記録

Horovodには、[Horovod Timeline](https://horovod.readthedocs.io/en/latest/timeline.html)と呼ばれる自分のアクティビティを記録する機能が備わっています。

:::note warn
**重要！**
Horovod Timelineはパフォーマンスに多大なる影響を及ぼします。Horovod Timelineを有効化すると、Inception3のスループットが~40%減少することがあります。HorovodRunnerを高速化したい場合には、Horovod Timelineを使わないでください。

トレーニングが進行中の状態でHorovod Timelineを参照することはできません。
:::

Horovod Timelineを記録するには、環境変数`HOROVOD_TIMELINE`をタイムラインファイルを保存したい場所に設定します。タイムラインファイルを容易に取得できるように共有ストレージ上の場所を使用することをお勧めします。例えば、以下のように[DBFSローカルファイルAPI](https://docs.databricks.com/dbfs/index.html)を使うことができます。

```py:Python
timeline_dir = "/dbfs/ml/horovod-timeline/%s" % uuid.uuid4()
os.makedirs(timeline_dir)
os.environ['HOROVOD_TIMELINE'] = timeline_dir + "/horovod_timeline.json"
hr = HorovodRunner(np=4)
hr.run(run_training_horovod, params=params)
```

そして、トレーニング関数の最初と最後にタイムライン固有のコードを追加します。以下のノートブックには、トレーニングの進捗を参照するワークアラウンドとして使用できるサンプルコードが含まれています。

## Horovod Timelineのサンプルノートブック

https://docs.databricks.com/_static/notebooks/machine-learning/horovod-runner-example.html

タイムラインファイルをダウンロードするには、[Databricks CLI](https://docs.databricks.com/dev-tools/cli/index.html)か[FileStore](https://qiita.com/taka_yayoi/items/01d9b69d2f5283d27d96)を使用し、参照するには以下のようにChromeブラウザの`chrome://tracing`機能を使用します。
![](https://docs.databricks.com/_images/mnist-timeline.png)

# 開発のワークフロー

シングルノードのディープラーニングのコードを分散トレーニングに移行するための一般的なステップが存在します。このセクションの[サンプル](#サンプル)では、これらのステップを説明しています。

1. **シングルノードのコードを準備する:** TensorFlow、Keras、PyTorchを用いたシングルノードのコードを準備し、テストします。
1. **Horovodに移行する:** Horovodを用いたコードに移行し、ドライバー上でテストするために[Horovod usage](https://github.com/horovod/horovod#usage)の手順に従います。

    1. Horovodを初期化するために`hvd.init()`を追加します。
    1. `config.gpu_options.visible_device_list`を用いてこのプロセスで使用されるサーバーGPUをピン留めします。プロセスごとに1つのGPUを用いる典型的なセットアップを行うと、これはローカルのランクに設定されます。この場合、サーバーの最初のプロセスには最初のGPUが割り当てられ、2番目のプロセスには2番目のGPUが割り当てられ、以降も同様となります。
    1. データセットのシャードを含めます。データセットオペレーターを用いることで、それぞれのワーカーがユニークなサブセットを読み込めるようになるので、分散トレーニングの実行においては非常に有用です。
    1. ワーカーの数で学習率をスケールさせます。ワーカーの数で、同期分散トレーニングの効率的なバッチサイズをスケールさせることができます。学習率の増加は、バッチサイズの増加を埋め合わせることになります。
    1. `hvd.DistributedOptimizer`でオプティマイザーをラッピングします。分散オプティマイザーは、オリジナルのオプティマイザーに徐々に計算処理を移譲し、allreduceやallgatherを用いて勾配を平均し、平均された勾配を適用します。
    1. ランク0から他のプロセスに初期変数の状態をブロードキャストするために、`hvd.BroadcastGlobalVariablesHook(0)`を追加します。トレーニングがランダムな重みからスタートしたり、チェックポイントから復旧した際に全てのワーカーで一貫性のある初期化が行われるようにするためにはこれが必要となります。あるいは、`MonitoredTrainingSession`を使用している場合には、グローバル変数が初期化された後に`hvd.broadcast_global_variables`オペレーションを実行することができます。
    1. 他のワーカーの挙動を破壊しないようにするために、ワーカー0でのみチェックポイントを保存するようにコードを変更します。
1. **HorovodRunnerに移行する:** HorovodRunnerはPython関数を呼び出すことでHorovodトレーニングジョブを実行します。メインのトレーニング処理を単一のPython関数でラップする必要があります。そして、ローカルモードでHorovodRunnerをテストしてから、分散モードでテストすることができます。

# ディープラーニングライブラリのアップデート

:::note
**注意**
本書にはDatabricksでは使用しない*slave*という単語への参照が含まれています。ソフトウェアからこの単語が削除された場合には、本書からも削除します。
:::

TensorFlow、Keras、PyTorchをアップグレード、ダウングレードする際は、新たにインストールしたライブラリを用いてコンパイルしたHorovodを再インストールす必要があります。例えば、Tensorflowをアップグレードしたい場合、[TensorFlow installation instructions](https://docs.databricks.com/applications/machine-learning/train-model/tensorflow.html)にあるinitスクリプトを使用し、最後に以下のTensorFlow固有のHorovodインストールコードを追加することをお勧めします。PyTorchなどのライブラリのアップグレード、ダウングレードのような異なる組み合わせに関しては、[Horovod installation instructions](https://github.com/horovod/horovod#install)をご覧ください。

```bash:Bash
add-apt-repository -y ppa:ubuntu-toolchain-r/test
apt update
# Using the same compiler that TensorFlow was built to compile Horovod
apt install g++-7 -y
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 --slave /usr/bin/g++ g++ /usr/bin/g++-7

HOROVOD_GPU_ALLREDUCE=NCCL HOROVOD_CUDA_HOME=/usr/local/cuda pip install horovod==0.18.1 --force-reinstall --no-deps --no-cache-dir
```

# サンプル

[MNIST](https://en.wikipedia.org/wiki/MNIST_database)データセットを用いた以下のサンプルでは、シングルノードのディープラーニングプログラムを、HorovodRunnerを用いてどのように分散ディープラーニングに移行するのかをデモしています。

- [Distributed deep learning training using TensorFlow with HorovodRunner for MNIST](https://docs.databricks.com/applications/machine-learning/train-model/distributed-training/mnist-tensorflow-keras.html)
- [Single node PyTorch to distributed deep learning](https://docs.databricks.com/applications/machine-learning/train-model/distributed-training/mnist-pytorch.html)

# 制限

- ワークスペースで[Reposのファイルサポート](https://qiita.com/taka_yayoi/items/b89f199ff0d3a4c16140#databricks-repos%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E4%BB%BB%E6%84%8F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%9C%89%E5%8A%B9%E5%8C%96)が有効化されており、`np`が1より大きい値に設定され、ノートブックがRepoにあるファイルからimportしている場合、HorovodRunnerは動作しません。`HorovodRunner`ではなく[horovod\.spark](https://docs.databricks.com/applications/machine-learning/train-model/distributed-training/horovod-spark.html)を使用することを検討してください。

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)