Apple Silicon M1 でtensorflow-macosを実行したらめちゃくちゃ速かった。

# はじめに
Macbook Air  (:apple:AppleSilicon, M1)を購入しました。
Appleが設計したM1は下馬評以上の性能を叩き出し、とても盛り上がっていますね。

M1のハードウェアとしての魅力はもちろんすごいですが、M1に合わせた各種ソフトウェアの最適化も魅力的です。

AppleがTensorflowをフォークしてM1で最高のパフォーマンスを発揮するように最適化したコード(tensorflow-macos)を公開しています。
https://github.com/apple/tensorflow_macos#requirements

tensorflowのブログ記事でパフォーマンスが比較されていますが、前世代のIntel Macと比べると信じられないほどの性能向上を見ることができます。
https://blog.tensorflow.org/2020/11/accelerating-tensorflow-performance-on-mac.html

# この記事では

1. 実際にM1 Macでtensorflow-macosを動作させた過程を紹介します。
2. サンプルコードを動作させてM1 Mac, Intel Mac, RTX 2080との実行時間の比較をします。


# 目次
<!-- タイトルとアンカー名を編集 -->
1. [```tensorflow-macos```を実行する環境を構築する。](#Chapter1)
1. [実行時間を比較する。](#Chapter2)
1. [考察、議論](#Chapter3)
1. [参考文献](#reference)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# ```tensorflow-macos```を実行する環境を構築する。

### Python 3.8 (for ARM)をインストールする。

`tensorflow-macos 0.1a0`は`Python 3.8`で動作します。
`tensorflow-macos`リポジトリの`REQUIREMENTS`にあるように、
必ず`Xcode Command Line Tools`からインストールしてください。

```bash
$ xcode-select --install
```
```bash
$ python3 -V
Python 3.8.2

$ which python3 
/usr/bin/python3
```

`tensorflow_macos`はARMアーキテクチャ向けのコードになっているので、`Python`もARMアーキテクチャ向けのものにする必要があります。

例えば以下のようにインストールしたものは`tensorflow-macos`の実行時に`wrong architecture`というようなエラーが発生しました。

~~~
・ Python公式サイトからdmgファイルをダウンロードしてインストール
・ Anacondaを使ってインストール
・ pyenvを使ってインストール
~~~
```python
Traceback (most recent call last):
  File "/path/to/tensorflow_macos_venv/lib/python3.8/site-packages/tensorflow/python/pywrap_tensorflow.py", line 64, in <module>
    from tensorflow.python._pywrap_tensorflow_internal import *
ImportError: dlopen(/path/to/tensorflow_macos_venv/lib/python3.8/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so, 6): no suitable image found.  Did find:
	/path/to/tensorflow_macos_venv/lib/python3.8/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so: mach-o, but wrong architecture
	/path/to/tensorflow_macos_venv/lib/python3.8/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so: mach-o, but wrong architecture
```

### `tensorflow-macos`の仮想環境(venv)を構築する。

`tensorflow_macos`の実行に必要な仮想環境を構築してくれる便利なスクリプトをAppleが用意してくれています。

`tensorflow-macos`リポジトリの`INSTALLATION`にあるように、ターミナルからスクリプトを実行します。

```bash
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/apple/tensorflow_macos/master/scripts/download_and_install.sh)"
```

仮想環境のパッケージのダウンロードが始まるので、しばらく待ちます(結構待つ)。
仮想環境を構築するパスを聞かれるので、Enterキーを押してデフォルトパスを指定し、構築を開始します。

```bash
Downloading installer.
/var/folders/hn/g5s5s0fx6zs2plvygdc95y140000gn/T/tmp.haPEac7f ~/Projects/tensorflow-mac
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   659  100   659    0     0   1331      0 --:--:-- --:--:-- --:--:--  1331
100  316M  100  316M    0     0  2467k      0  0:02:11  0:02:11 --:--:-- 1638k
Extracting installer.
Path to new or existing virtual environment [default: /Users/tomoyaeibu/tensorflow_macos_venv/]: 
Using python from /usr/bin/python3.

###########################################################################

./install_venv.sh will perform the following operations:
-> Create new python 3.8 virtual environment at /Users/tomoyaeibu/tensorflow_macos_venv.
-> Install tensorflow_macos 0.1a0 into created virtual environment /Users/tomoyaeibu/tensorflow_macos_venv.
-> Install bundled binary wheels for numpy-1.18.5, grpcio-1.33.2, h5py-2.10.0, tensorflow_addons-0.11.2+mlcompute into /Users/tomoyaeibu/tensorflow_macos_venv.


Confirm [y/N]? 
```

### ```tensorflow-macos```の仮想環境(venv)をアクティベートする。

これで```tensorflow-macos```を実行する環境は整いました。
簡単ですね・・・！

`tensorflow-macos`の仮想環境をアクティベートします。

```bash
$ source /Users/{username}/tensorflow_macos_venv/bin/activate

$ pip list         
Package                Version
---------------------- ---------
absl-py                0.11.0
appnope                0.1.0
astunparse             1.6.3
backcall               0.2.0
cached-property        1.5.2
cachetools             4.1.1
certifi                2020.11.8
chardet                3.0.4
decorator              4.4.2
flatbuffers            1.12
gast                   0.4.0
google-auth            1.23.0
google-auth-oauthlib   0.4.2
google-pasta           0.2.0
grpcio                 1.33.2
h5py                   2.10.0
idna                   2.10
ipython                7.19.0
ipython-genutils       0.2.0
jedi                   0.17.2
Keras-Preprocessing    1.1.2
Markdown               3.3.3
numpy                  1.18.5
oauthlib               3.1.0
opt-einsum             3.3.0
parso                  0.7.1
pexpect                4.8.0
pickleshare            0.7.5
pip                    20.2.4
prompt-toolkit         3.0.8
protobuf               3.14.0
ptyprocess             0.6.0
pyasn1                 0.4.8
pyasn1-modules         0.2.8
Pygments               2.7.2
requests               2.25.0
requests-oauthlib      1.3.0
rsa                    4.6
setuptools             50.3.2
six                    1.15.0
tensorboard            2.4.0
tensorboard-plugin-wit 1.7.0
tensorflow-addons      0.11.2
tensorflow-estimator   2.3.0
tensorflow-macos       0.1a0
termcolor              1.1.0
traitlets              5.0.5
typeguard              2.10.0
typing-extensions      3.7.4.3
urllib3                1.26.2
wcwidth                0.2.5
Werkzeug               1.0.1
wheel                  0.35.1
wrapt                  1.12.1

```

`tensorflow-macos`がインストールされていることがわかります。

### `tensorflow-macos`を実行する。

`tensorflow-macos`を実行するために既存のtensorflowを使ったトレーニングやバリデーションのコードを修正する必要はありません。

今まで使っていたスクリプトをそのまま使えます。
（ただしKerasを使っていた場合、ARM版のKerasがまだないようなので、`tensorflow`のAPIに置き換えるか、`tf.Keras`のAPIに置き換えてください。）

### 追記

@asamiKAさんが[Apple Silicon M1 は自然言語処理も、ちょっと速いよ](https://qiita.com/asamiKA/items/27ea834df1e29480a3ca)にて、より実践的な問題に対して`tensorflow-mac`を適用しています。

記事によると、`tensorflow-mac`はtensorflowで使うことのできるすべてのレイヤーについて最適化しているわけではないようです。
最適化されていないレイヤーを使う場合は、CPUのみが使われるため、学習スピードが遅くなってしまうはずです。
記事では最適化されていないGRUレイヤーを、最適化されているLSTMレイヤーに置き換えています。

将来的に`tensorflow-mac`はtensorflowにマージされる予定があるらしいのですが、その時までには対応するレイヤーが増えていくのだと思います。

<a id="#Chapter2"></a>
# 実行時間を比較する。

早速サンプルコードを実行して実行時間を比較してみましょう。
トレーニングにかかる時間を比較します。

### 実行環境

以下の3つの環境で比較しました。

| |Macbook Air (2020,M1)|Macbook Pro (2017,Intel)|開発に使っている学習用PC|
|:--|:--|:--|:--|
|OS|Mac Big Sur|Mac Big Sur|Ubuntu 20.04.1|
|CPU|M1|2.3GHzデュアルコアIntel Core i5|Core i7-8700 3.2GHz|
|GPU|M1|Intel Iris Plus Graphics 640|RTX 2080 8GB|
|RAM|8GB|8GB|16GB|
|Python|python 3.8.2|python 3.8.2|python 3.8.2|
|Tensorflow|tensorflow-macos 0.1a0|tensorflow-macos 0.1a0|tensorflow 2.4|

サンプルコードにはシンプルなmnistのクラス分類を使っています。

```Python

import tensorflow as tf
mnist = tf.keras.datasets.mnist
import time

(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(512, activation=tf.nn.relu),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

start = time.perf_counter()
model.fit(x_train, y_train, epochs=5)
end = time.perf_counter()
print(f"Elapsed time : {end - start:.3f} s.")

model.evaluate(x_test, y_test)
```

### 実行結果

| |Macbook Air (2020,M1)|Macbook Pro (2017,Intel)|開発に使っている学習用PC|
|:--|:--|:--|:--|
|5epochにかかった時間|6.268 s|23.876 s|8.053 s|

Intel Macに対してM1 Macの方が4倍程度、速いという結果になりました。

素晴らしく速いですね。
たしかMacbook Pro(2017)の方がMacbook Air(2020)よりも値段が高かったような気がするんですが・・・。

~~さらにいつも開発に使っているRTX 2080よりも速かったのはもっと驚きの結果でした。~~

~~RTX 2080の方は特にチューニング作業はしていないので、使い方次第では実はもっと速くなる可能性はありますが、M1 Macだと何も気にせずこのぐらいの速度でトレーニングできるのは素晴らしいです。~~

~~これからDeep Learningをやりたくて個人で開発環境を整えるのであれば、下手に高いグラフィックボードを購入するよりは、M1が搭載されたMadbook Airを買った方がコスパは良いと思います。~~

### 追記
@nowokayさんから「RTX 2080の環境はGPUのコアを計算処理にフルに使っていないために、データ転送がボトルネックになっているのではないか」という指摘をいただきました。

実際に上記のスクリプトの実行中にRTX 2080の環境で`nvidia-smi`をして確認したところ、GPUの使用率が15%でした。
そこでアドバイスの通りにレイヤーをConv2Dに変更したり、フィルタ数を大きくすることでRTX 2080の環境でのGPUの使用率を95%まで上げ、再度比較をしました。

``` Python
import tensorflow as tf
mnist = tf.keras.datasets.mnist
import time

(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
 
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)
x_test  = x_test.reshape(x_test.shape[0], 28, 28, 1)
 
model = tf.keras.models.Sequential([
  tf.keras.layers.Conv2D(1024, (3,3), activation='relu', input_shape=(28, 28, 1)),
  tf.keras.layers.MaxPooling2D(2,2),
  tf.keras.layers.Conv2D(512, (3,3), activation='relu'),
  tf.keras.layers.MaxPooling2D(2,2),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(256, activation='relu'),
  tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
 
start = time.perf_counter()
model.fit(x_train, y_train, epochs=5)
end = time.perf_counter()
print("Elapsed time : {0} s.".format(end-start))
 
model.evaluate(x_test, y_test)

```

(※ Macbook Pro 2017はメルカリで売ってしまったので比較ができませんでした。)

| |Macbook Air (2020,M1)|開発に使っている学習用PC|
|:--|:--|:--|
|5epochにかかった時間|2629.628 s|135.903s|

RTX 2080の方が圧倒的に短い時間で学習できました。

それぞれの環境の性能の違いがどれくらいなのかは、どこがボトルネックになっているかなど正確に比較する必要がありそうです。
実行時間の比較については参考程度にしてください。

@nowokayさんに指摘されるまでは不適切な条件での実験になっていました。
申し訳ありませんでした。そしてご指摘ありがとうございました！

<a id="#Chapter3"></a>
# 考察、議論

### GPU? CPU? Neural Engine?

`tensorflow-mac`では、
以下のように使用するデバイスを選択するオプションが用意されています。

```Python
from tensorflow.python.compiler.mlcompute import mlcompute
mlcompute.set_mlc_device(device_name="cpu")

# cpu : CPUだけを利用する。
# gpu : GPUだけを利用する。
# any : 最適なデバイスを使用する。(デフォルト)
```

Intel Macの場合、速い順に`any` > `gpu` > `cpu` でした。
`cpu`より`gpu`の方が速いのは、Intel Iris Plus Graphicsを使っているからだと思いますが、
`gpu`より`any`の方が速いのは、「最適なデバイス」を使っているからなのでしょうか。

M1 Macの場合、速い順に`any` > `cpu`  > `gpu`でした。
`gpu`が遅いのはEagar Mode (Define by run)に対応していないだけなので、Eagar modeをオフにすれば速くなるかもしれません。
`any`が速いのは、やはり「最適なデバイス」を使っているからで、FowardにはNeural Engineを、BackwardにはGPUを使うなどの工夫がしてあるのかな？などと考察しています。

これらのデバイスが同じメモリを共有しているのがM1の大きな特徴の一つですし。

どこかに詳しい記事がないかを探してみます。


<a id="#reference"></a>
# 参考文献
- [tensorflow-macosのGithubリポジトリ](https://github.com/apple/tensorflow_macos)
- [Accelerating TensorFlow Performance on Mac (Tensorflow Blog)](https://blog.tensorflow.org/2020/11/accelerating-tensorflow-performance-on-mac.html)
- [macOS に最適化された TensorFlow を Apple M1 チップ 環境で import するまでの Tips
Python
](https://qiita.com/forest1988/items/4b6bb63e3438dea0c832)
- [Apple Silicon M1 は自然言語処理も、ちょっと速いよ](https://qiita.com/asamiKA/items/27ea834df1e29480a3ca)