旧型PCでNGCのCUDAイメージを使いTensorFlow 1.12をソースからビルドした話

エヌビディアの佐々木です。

※ なお、私が所属を明示して記事を書くのは、所属を明示せずに「TITAN RTX最高！一番好きなGPUです」などと書くとステマ以外の何物でもなくなってしまうからであって、内容自体は会社としての見解ではなく個人的なものです。

さて、この記事は恥ずかしい告白であり、書かない方がなんぼかマシなのではないかと葛藤したのですが、あえて書いてみることにしました。同じ境遇の方がたくさんいるとは思えませんが、どなたかの役に立てば幸いです。

# Lynnfield + Pascal

歴史的な事情もあり私はクラウドが好きです。オンプレはクラウドのキャッシュであり踏み台、手元のPCはsshが動けば何でもいいやと思っていたので、自宅のデスクトップPCは[HPのZ200 SFF](http://jp.ext.hp.com/lib/doc/catalog/workstation/jwa10436_01.pdf) (上司が捨てようとしてたのを貰った) を未だに使っています。このPC、GeForce GTX 1050 Tiを後からくっつけたので、10年近く前の[Core i7 860](https://ark.intel.com/ja/products/41316/Intel-Core-i7-860-Processor-8M-Cache-2-80-GHz-)にPascal世代GPUが付くというジェネレーションギャップ構成になっています。

先日、この環境に[NGCのTensorFlowイメージ](https://ngc.nvidia.com/catalog/containers/nvidia%2Ftensorflow)を持ってきて起動したところ、けしからんことに何度試してもすぐcoreを吐いて死んでしまうではありませんか。調べてみると、[TensorFlow 1.6.0のリリースノート](https://github.com/tensorflow/tensorflow/releases/tag/v1.6.0)に
> Prebuilt binaries will use AVX instructions. This may break TF on older CPUs.

とある通り、1.6.0以降のバイナリはAVX拡張をサポートしていないCPUでは動かなくなっていたのです。これ自体はそんなにおかしな変更だとは思いません。だって、AVXの登場はSandy Bridgeの頃ですから。が、しかし、今ここにあるのはCore i7 860、GPUで言えばFermi世代のGTX 580より古いんだ…

# 地べたでビルドする？

恥ずかしながら私はTensorFlowをソースからビルドしたことが一度もありません。となればこれは良い機会なのではないか、この際だからバイナリパッケージやコンテナに頼らず、素のUbuntu上でビルドしてみよう。[手順の公式な解説](https://www.tensorflow.org/install/source)もあるし、簡単だろう。

しかし、ここで問題が。私の環境、ハードウェアは10年ものなんですけど、ソフトはUbuntu 18.04 + CUDA 10のわりと新しい構成なんです。[TensorFlowのGPU要件](https://www.tensorflow.org/install/gpu)によれば、サポートされているのはCUDA 9.0です。しかし、CUDA 9.0はUbuntu 18.04をサポートしていないのでした。それに、やっぱりホストの環境をガチャガチャ汚すのは気が引けます。やっぱりコンテナを使うか…

# NGCのCUDAコンテナでビルドする

なななんと！[NGC](https://ngc.nvidia.com/)にはUbuntu 16.04 + CUDA 9.0という土台としてちょうど良いイメージがあります。ベンリダナー。この中でTensorFlow 1.12をビルドしてみることにしました。

まず、イメージをpullしてきます。
`docker pull nvcr.io/nvidia/cuda:9.0-devel-ubuntu16.04`

コンテナを起動し、中で nvidia-smi してみました。ドライバはホストのもの使うので、CUDA Versionが10.0と表示されてしまいますが、nvcc --version の結果を見るとCUDA 9.0環境であることがわかります。

```
root@bebffa3e5dc0:/# nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 410.79       Driver Version: 410.79       CUDA Version: 10.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 105...  Off  | 00000000:01:00.0  On |                  N/A |
| 43%   43C    P8    N/A /  75W |   1584MiB /  4038MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
root@bebffa3e5dc0:/# nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation
Built on Fri_Sep__1_21:08:03_CDT_2017
Cuda compilation tools, release 9.0, V9.0.176
```

では、ビルドの準備を始めます。基本的には[公式ドキュメント](https://www.tensorflow.org/install/source)通りですが、コンテナの中はいろいろ最小限なので、追加で導入したものがいくつかあります。

## Python 関連

この辺は特に問題ありません。インストールするだけ。

```
apt update
apt install python-dev python-pip
pip install -U pip six numpy wheel mock
pip install -U keras_applications==1.0.5 --no-deps
pip install -U keras_preprocessing==1.0.3 --no-deps
```
## Bazelはバージョンに注意

ビルドツールの[Bazel](https://docs.bazel.build/versions/master/install.html)をインストールしますが、最初はここで失敗しました。最新のBazelを使うと、configureが終わってビルドを開始した瞬間に、以下のエラーで一歩も先に進めなくなります。

```
ERROR: Config value cuda is not defined in any .rc file
```

調べてみるとこちらのIssueがどんぴしゃり。
[Build from source -> build the pip package -> GPU support -> bazel build -> ERROR: Config value cuda is not defined in any .rc file #23401](https://github.com/tensorflow/tensorflow/issues/23401)

>Need to downgrade bazel version to 0.16.1

とあったので[Bazel 0.16.1](https://github.com/bazelbuild/bazel/releases/download/0.16.1/bazel-0.16.1-installer-linux-x86_64.sh)を入れ直します。

なお、Bazelをダウンロードしようにもコンテナのなかにはcurlもwgetもありません。インストールしても良いのですが、今回はホストでダウンロードしてから docker cp で送り込みました。(ボリュームをマウントしてなかったので)


## cuDNN

cuDNNも下記の2ファイルをホストでダウンロードして、docker cpでコンテナへ送りました。

```
libcudnn7_7.4.1.5-1+cuda9.0_amd64.deb
libcudnn7-dev_7.4.1.5-1+cuda9.0_amd64.deb
```

## TensorFlowのソースを持ってくる

やっとTensorFlowにたどり着きました。GitHubのリポジトリをコピー…しようと思ったらgitもなかったのでインストールしてから。

```
apt install git
git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow
git checkout r1.12
```

つぎに ./configure します。GPU関連の項目は次のように答えました。(GTX 1050 TiのCompute Capabilityは6.1です)

```
Do you wish to build TensorFlow with CUDA support? [y/N]: Y

Please specify the CUDA SDK version you want to use. [Leave empty to default to CUDA 9.0]: 9.0

Please specify the location where CUDA 9.0 toolkit is installed. Refer to README.md for more details. [Default is /usr/local/cuda]:

Please specify the cuDNN version you want to use. [Leave empty to default to cuDNN 7.0]: 7.0

Please specify the location where cuDNN 7 library is installed. Refer to README.md for more details. [Default is /usr/local/cuda]:

Please specify the NCCL version you want to use. If NCLL 2.2 is not installed, then you can use version 1.3 that can be fetched automatically but it may have worse performance with multiple GPUs. [Default is 2.2]: 1.3

Please specify a list of comma-separated Cuda compute capabilities you want to build with.
You can find the compute capability of your device at: https://developer.nvidia.com/cuda-gpus.
Please note that each additional compute capability significantly increases your
build time and binary size. [Default is: 3.5,7.0] 6.1
```

## ようやくビルドする

下記のコマンドでビルドを開始します。

```
bazel build --config=opt --config=cuda //tensorflow/tools/pip_package:build_pip_package
```

後は待つだけだろうと思ったら、「ImportError: No module named enum」なるエラーで停止してしまいました。

EnumモジュールはPython 2.7にはないんですね…バックポートされたenum34モジュールというのがあるそうなのでそれをインストールして、

```
pip install enum34
```

再度ビルドを試したところ、今度はうまくいきました。

```
Target //tensorflow/tools/pip_package:build_pip_package up-to-date:
  bazel-bin/tensorflow/tools/pip_package/build_pip_package
INFO: Elapsed time: 135.983s, Critical Path: 53.71s
INFO: 49 processes: 49 local.
INFO: Build completed successfully, 90 total actions
```

## パッケージの作成

ビルドの結果生成された bazel-bin/tensorflow/tools/pip_package/build_pip_package を実行して、wheel ファイルを作成します。

```
tensorflow-1.12.0-cp27-cp27mu-linux_x86_64.whl
```

というのができました。後はこれを pip install すればよし。ついでに、このコンテナを commit して新たなイメージを生成しておきました。

# まとめ

- AVX拡張がサポートされていないためビルド済みのTensorFlowパッケージが使えない環境で、TensorFlow 1.12をソースからビルドしました。
- ホストはUbuntu 18.04 + CUDA 10だけどTensorFlowはCUDA 9.0を求めるので、ビルド環境に[NGC](https://ngc.nvidia.com/)のUbuntu 16.04 + CUDA 9.0コンテナイメージを使いました。
- Bazelは最新版じゃダメ。0.16.1を使えば大丈夫。
- 複数のソフトウェアモジュールのバージョン組み合わせを試行錯誤するの大変だし不毛なのでやっぱりコンテナを活用したいな…

# 関連情報

[TensorFlow: Build from source](https://www.tensorflow.org/install/source)
[TensorFlow: GPU Support](https://www.tensorflow.org/install/gpu)
[新しいBazelだとビルドに失敗する件](https://github.com/tensorflow/tensorflow/issues/23401)
[NGCのTensorFlowイメージ](https://ngc.nvidia.com/catalog/containers/nvidia%2Ftensorflow)
[NGCのCUDAイメージ](https://ngc.nvidia.com/catalog/containers/nvidia%2Fcuda)
[NGCへのサインアップ](https://ngc.nvidia.com/signup/register)