環境構築エラー解決バイブル 〜 PyTorch/cuda/numba/LLVMなどを倒す

# 数年前の機械学習コードが動かない？

あなたがAIの研究者であったり、AIに関連するサービスの開発者であるならば、少し昔の機械学習モデルを動かしたい機会が少なからずあることでしょう。

しかし、いざ`git clone`して`pip install -r requirements.txt`するとエラーがわんさか…ということはザラにあります。
全部インストール出来たのに、実行段階でエラーになる事もしばしば…

以下では、そのような状況を打開するための方法を、私個人の経験に基づいて説明しようと思います。

使用するOSはUbuntu、実行環境はローカルまたは社内サーバー等を想定しています。

# 必ず使用すべきツール

以下は私が強く推奨するツールです。絶対に使った方が良いので今すぐインストールして下さい。

- `pyenv`: Pythonのバージョンを自由に変えられる
- `venv`: Pythonのパッケージをプロジェクト毎に独立させる (インスコ不要)
- `docker`: システムの環境をプロジェクト毎に独立させる
- `aptitude`: システムパッケージの依存を賢く解決する
- `update-alternatives`: cudaのバージョン等を変えられる (多分インスコ不要)

その他に、慣れておいた方がよいコマンドは

- `add-apt-repository`: aptリポジトリの表示・追加・削除
- `apt-key`: aptリポジトリのキーの表示・追加・削除

ですかね。

# 結論

最初に結論を書いてしまいますが、次のセクションでより具体的なコマンドなども紹介するのでそちらもご覧ください。
私は以下のような流れで環境構築をすると良いと思います。

## 最初にやること

1. `pyenv`でPythonのバージョンを合わせる
2. `venv`環境を作る
3. `requirements.txt`を一括でインストールしてみる
    1. インストール出来たらラッキー✌️
        a. `update-alternatives`でcudaのバージョンを切り替えよう
    2. インストール出来なかったら…
        a. `requirements.txt`にあるパッケージを個別にインストールする
        b. エラーの原因となっているパッケージを特定する
        c. そのパッケージを、バージョンを様々に変えてインストールしてみる
        d. もしシステムパッケージが必要なら、`aptitude`でインストール
4. 以上でダメなら`Docker`に頼る
    1. 早めの諦めが肝心
    2. Dockerfileを書くのは面倒なので書かない

## Dockerでやること

1. `requirements.txt`を見てtorchのバージョンを確認
2. そのtorchのバージョンに対応するcudaのバージョンを[例のサイト](https://download.pytorch.org/whl/torch_stable.html)からエスパーする
    1. インストール時:
        ```shell-session
        # pip3 install torch==X.X.X+cuXXX -f https://download.pytorch.org/whl/torch_stable.html
        ```
3. さらに、必要なUbuntuのバージョンを探る
    1. 必要なシステムパッケージが`aptitude`でインストールできるものを選ぶ
4. [Docker Hub](https://hub.docker.com/r/nvidia/cuda/tags)から`nvidia/cuda`イメージのタグを探す
    1. 例: `<cuda ver.>-cudnnX-runtime-ubuntuXX.04`
5. イメージをpullしてインタラクティブシェルを作成
    ```shell-session
    $ docker pull nvidia/cuda:<見つけたタグ>
    $ docker run --gpus all -it nvidia/cuda:<タグ> /bin/bash
    # apt update; apt install aptitude; aptitude install curl git
    # aptitude install python3.X python3-pip
    ```
    1. Dockerコンテナの中なら`pyenv`や`venv`は使わなくてもいいんじゃない？
    2. Pythonの起動は`python`ではなく`python3.X`
9. 最初にcudaが機能しているかどうかを確認する事が重要！！
    ```shell-session
    # pip3 install torch==X.X.X+cuXXX -f https://download.pytorch.org/whl/torch_stable.html
    # python3.X
    >>> import torch
    >>> torch.version.cuda
    >>> torch.cuda.is_available()
    >>> torch.Tensor([1, 2, 3]).cuda()
    ```
10. `# git clone`ののち`# pip3 install -r requirements.txt`
    1. ここでも同様に、エラーとなるパッケージを個別にバージョン指定してインストールすると良かったりする😇
11. 全て動いたら新しいDockerイメージとして保存しよう！
    1. `$ docker commit xxxxxxx <タグ名>`

# 具体例と細かいテク

では、実際に現在の一般的な環境 (Ubuntu22.04LTSを想定) では動かないコードを動かしてみましょう。
ここでは、音声合成モデルである[Grad-TTS](https://github.com/huawei-noah/Speech-Backbones/tree/main/Grad-TTS)を動かします。

https://github.com/huawei-noah/Speech-Backbones/tree/main/Grad-TTS

## venvでのインストール①: numbaとの戦い

`README`のInstallationのところにPython3.6が使われているとありますので、Python3.6を使うための環境を整えましょう。
```shell-session
$ git clone https://github.com/huawei-noah/Speech-Backbones.git
$ cd SpeechBackbones/Grad-TTS/
$ pyenv install 3.6
$ pyenv global 3.6
$ python -m venv .venv
$ source .venv/bin/activate
```
:::note info
インストールされているPythonのバージョン一覧は`$ pyenv versions`で確認できます。
:::
:::note info
venvの有効化後に`python`コマンドや`pip`が正しく設定されているかどうかは`$ which pip`などとして確認できます。pipでインストールされたパッケージ一覧は`$ pip list`で確認可能です。
:::

依存関係をインストールします:
```shell-session
(.venv) $ pip install -r requirements.txt --no-cache-dir 
```
失敗しました。ログを見ると、
> Collecting numba>=0.43.0 (from librosa==0.8.0->-r requirements.txt (line 8))

の箇所で失敗しています。`requirements.txt`を見ると、`librosa==0.8.0`が依存に入っており、これが`numba`に依存しているようです。さらに、エラーメッセージをよく見ると
> distutils.errors.DistutilsError: Couldn't find a setup script in /tmp/easy_install-yyg21p65/numpy-2.1.3.tar.gz

と出ており、`numpy==2.1.3`が発見できなかったと分かります。それもそのはずで、Python3.6がサポートされている`numpy`のバージョンは`1.19.5`までです (`$ pip install numpy==2.1.3`とすると分かります) 。

これは、**pipが出来るだけ新しいバージョンの`numba`をインストールしようとした事が原因である**可能性があります。よって、最小限のバージョンの`numba`をインストールしてみましょう:
```shell-session
(.venv) $ pip install numba==0.43.0 --no-cache-dir
```
今度は以下のようなエラーが出ました:
> RuntimeError: Building llvmlite requires LLVM 10.0.x or 9.0.x, got '14.0.0'. Be sure to set LLVM_CONFIG to the right executable path.

これは、`numba==0.43.0`が依存している`llvmlite`は`LLVM 10.0.x`を必要とするのに、それがシステムにインストールされていないというエラーです (`LLVM 14.0.0`はインストールされています) 。

`numba==0.43.0`では古すぎたと考えられるので、今度は`numba==0.50.0`をインストールしてみましょう:
```shell-session
(.venv) $ pip install numba==0.50.0 --no-cache-dir
```
> Installing collected packages: llvmlite, numba
> Successfully installed llvmlite-0.33.0 numba-0.50.0

上手く行きました！

## venvでのインストール②: matplotlibとの戦い

では、`requirements.txt`のインストールをもう一度試しましょう:
```shell-session
(.venv) $ pip install -r requirements.txt --no-cache-dir
```
今度は、`numba>=0.43.0`に対する依存が
> Requirement already satisfied: numba>=0.43.0 in ./.venv/lib/python3.6/site-packages (from librosa==0.8.0->-r requirements.txt (line 8)) (0.50.0)

となり、無事解決できました。しかし別のエラー:
> Running setup.py install for pillow ... error

が出てしまいました。パッケージ`pillow`をインストールする際にエラーが出ているようですが、`requirements.txt`には`pillow`に対する依存が書かれていません。そこで、どのパッケージが`pillow`に依存しているのかを調べると、
> Collecting pillow>=6.2.0 (from matplotlib==3.3.3->-r requirements.txt (line 4))

というログが見つかりました。`matplotlib`が`pillow`に依存していたようですね。

例によって、最小限のバージョンの`pillow`をインストールすることを試みましょう:
```shell-session
(.venv) $ pip install pillow==6.2.0 --no-cache-dir
```
> Installing collected packages: pillow
> Successfully installed pillow-6.2.0

通りました。それではもう一度、`requirements.txt`に挑戦します:
```shell-session
(.venv) $ pip install -r requirements.txt --no-cache-dir
```
> Successfully installed Unidecode-1.1.2 appdirs-1.4.4 audioread-3.0.1 certifi-2024.8.30 cffi-1.15.1 charset-normalizer-2.0.12 decorator-5.1.1 idna-3.10 importlib-resources-5.4.0 inflect-5.0.2 joblib-1.1.1 librosa-0.8.0 packaging-21.3 pooch-1.6.0 pycparser-2.21 requests-2.27.1 resampy-0.4.3 scikit-learn-0.24.2 scipy-1.5.4 soundfile-0.12.1 threadpoolctl-3.1.0 tqdm-4.64.1 urllib3-1.26.20 zipp-3.6.0

インストール成功🎉

:::note info
GitHubのissuesをチェックすることも大切です。あなたが環境構築に苦しんでいる時、他の人も苦しんでいます。
:::

## venvでのインストール③: torch/cudaとの戦い

ようやく`requirements.txt`がインストール出来たので、次のステップに進むことが出来ます。`README`によれば`setup.py`を動かす必要があるとのことなので、それもやります。
```shell-session
(.venv) $ cd model/monotonic_align; python setup.py build_ext --inplace; cd ../..
```

ここで、torchが本当に動くのかをチェックしましょう:
```shell-session
(.venv) $ python
>>> import torch  # LIBTORCHやLD_LIBRARY_PATHを設定しているとここでエラーになることもある
>>> torch.version.cuda
'10.2'
>>> torch.cuda.is_available()
True
>>> torch.Tensor([1,2,3]).cuda()
RuntimeError: CUDA error: no kernel image is available for execution on the device
```

エラーになりました。つらすぎる。
しかし全ての原因はtorchとcudaのバージョンを合わせるのを忘れていることです。ここでは`cuda 10.2`が必要であると分かるので、これをインストールしましょう。

:::note info
既に何らかのバージョンのcudaをインストールしているのであれば、nvidiaのaptリポジトリ及びキーは追加しているものと思われますが、もしそうでなければそこからやって下さい。
:::

apt経由で利用できる`cuda`を探します:
```shell-session
(.venv) $ aptitude search cuda
```
> ...
> i   cuda                              - CUDA meta-package                           
> p   cuda-11-7                         - CUDA 11.7 meta-package                      
> p   cuda-11-8                         - CUDA 11.8 meta-package                      
> p   cuda-12-0                         - CUDA 12.0 meta-package
> ...

Oh…どうもUbuntu22.04LTSではcuda-11-7以降しかインスコできない。

待って！ あきらめるのはまだ早いよ！🤓

実は`cuda 10.2`以外のバージョンでも動くことがあります。`requirements.txt`を見ると`torch==1.9.0`がインストールされていますので、これに対応するcudaのバージョンを[例のサイト](https://download.pytorch.org/whl/torch_stable.html)からエスパーしましょう。

`cuda 10.2`の他に`cuda 11.1`が使えることが分かります。`cuda 11.1`を使う場合はtorchをインストールし直す必要があります。
```shell-session
(.venv) $ pip uninstall torch
(.venv) $ pip install torch==1.9.0+cu111 -f https://download.pytorch.org/whl/torch_stable.html
```

`cuda 11.1`と`cuda 11.7`は互換性があるかもしれない()ということで、`cuda 11.7`をインストールしてみましょう:
```shell-session
(.venv) $ sudo aptitude install cuda-11-7
(.venv) $ update-alternatives --config cuda  # インタラクティブにcuda-11.7を選択
(.venv) $ sudo reboot
$ nvcc -V
```
> nvcc: NVIDIA (R) Cuda compiler driver
> Copyright (c) 2005-2022 NVIDIA Corporation
> Built on Wed_Jun__8_16:49:14_PDT_2022
> Cuda compilation tools, release 11.7, V11.7.99
> Build cuda_11.7.r11.7/compiler.31442593_0


:::note info
`update-alternatives`を初めて使う方は、以下を参考に設定すると良いでしょう:
- [UbuntuでのCUDAのバージョンの切り替え方](https://www.nemotos.net/?p=2374)
- [CUDA Installation Guide for Linux](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#post-installation-actions)
:::

では、torchが動くかどうかを検証します:
```shell-session
$ source ./.venv/bin/activate
(.venv) $ python
>>> import torch
>>> torch.version.cuda
'11.1'
>>> torch.cuda.is_available()
True
>>> torch.Tensor([1,2,3]).cuda()
tensor([1., 2., 3.], device='cuda:0')
```

動きましたね。事前学習済みモデルをダウンロードして推論してみましょう:

```shell-session
(.venv) $ python inference.py -f ./resources/filelists/synthesis.txt -c ./checkpts/grad-tts.pt
```
> Initializing Grad-TTS...
> Number of parameters: 14835032
> Initializing HiFi-GAN...
> Removing weight norm...
> Synthesizing 0 text... Grad-TTS RTF: 0.015275078755040321
> Synthesizing 1 text... Grad-TTS RTF: 0.012218788207116787
> Synthesizing 2 text... Grad-TTS RTF: 0.01215144739797228
> Done. Check out `out` folder for samples.

スバラシイ

# Dockerを使ったインストール

上記の手順ではたまたま上手く行きましたが、`cuda 11.1`でしか動かないものを`cuda 11.7`上で動かすというのが気持ち悪いし、他のケースでは上手く行かないかも知れません。なので、以下ではDockerを用いた環境構築法もご紹介します。

## 各種バージョンのエスパー

今回のケースでは、
- OS: Ubuntu18.04 LTS
- torch: 1.9.0
- cuda: 11.1
- numba: 0.50.0
- pillow: 6.2.0

とするのが良いでしょう。OSがUbuntu18.04 LTSなのは`cuda 11.1`をインストールするためです。`cuda`や`numba`、`Pillow`のバージョンは、上に書いたような試行錯誤の末に得ることが出来ます。

これらに適合する`nvidia/cuda`イメージのタグを[Docker Hub](https://hub.docker.com/r/nvidia/cuda/tags)から探しましょう。`11.1.1-cudnn8-runtime-ubuntu18.04`が良さそうです。

## イメージのpullと各種インストール

取得したイメージから、インタラクティブシェルを立ち上げます。Dockerfileを書くよりも断然楽です。
```shell-session
$ docker pull nvidia/cuda:11.1.1-cudnn8-runtime-ubuntu18.04
$ docker run --gpus all -it nvidia/cuda:11.1.1-cudnn8-runtime-ubuntu18.04 /bin/bash
# apt update; apt install aptitude; aptitude install curl git
# aptitude install python3.6 python3-pip
# git clone https://github.com/huawei-noah/Speech-Backbones.git
# cd SpeechBackbones/Grad-TTS/
```

続いてPythonパッケージをインストールします:
```shell-session
# pip3 install numba==0.50.0 pillow==6.2.0
# pip3 install -r requirements.txt  # torchの行はコメントアウトしておくか、後でuninstallする
# pip3 install typing-extensions==4.1.1  # なんか知らないけど必要
# pip3 install torch==1.9.0+cu111 -f https://download.pytorch.org/whl/torch_stable.html
```

torchが使えるか否かを確認します(略)

事前学習済みモデルを`docker cp`などでコンテナ内に放り込み、推論スクリプトを動かしてみましょう:
```shell-session
# cd model/monotonic_align; python3.6 setup.py build_ext --inplace; cd ../..
# python3.6 inference.py -f ./resources/filelists/synthesis.txt -c ./checkpts/grad-tts.pt
```
> Initializing Grad-TTS...
> Number of parameters: 14835032
> Initializing HiFi-GAN...
> Removing weight norm...
> Synthesizing 0 text... Grad-TTS RTF: 0.015118690146169355
> Synthesizing 1 text... Grad-TTS RTF: 0.010802815066149635
> Synthesizing 2 text... Grad-TTS RTF: 0.011404373476001026
> Done. Check out `out` folder for samples.

動きました。このコンテナをイメージとして保存しましょう！

```shell-session
# exit
$ docker ps -a  # コンテナのIDを調べる
$ docker commit 6e3abbee3dc2 grad-tts
$ docker images  # 新しくイメージが出来たことを確認
```

:::note info
セットアップしたコンテナを元にDockerfileを書きたくなった場合、コマンドの履歴を元に手作業で書きましょう。`history`コマンドなどで今までの履歴を見ることが出来ます。
:::

:::note info
Dockerのイメージが多くなるとストレージを食うので、増設したSSD上にイメージを置いておくと良いです。増設したSSDを`/nvme1n1p1/`にマウントしたとすると、
```shell-session
$ sudo rsync -a --info=progress2 /var/lib/docker /nvme1n1p1/.docker
$ sudo rm -r /var/lib/docker
$ sudo ln -s /nvme1n1p1/.docker /var/lib/docker
```
といった感じでDockerイメージを全て`/nvme1n1p1/.docker/`上に移すことが出来ます。
:::


# まとめ

環境構築はとてもつらい。LGTMを押してください。コメントをして下さると泣いて喜びます。