ROCm + libtorch(C++) アプリをビルドするメモ(2020 年 1 月 15 日時点)

## 背景

* C/C++ で ROCm で機械学習(主に学習)したい.

ROCm(HIP) + libtorch で C++ で AMD GPU + torch の環境を作ってみます.

とりあえず簡単なプログラムが動くのは確認しました.

## 環境

* Ubuntu 18.04
* ROCm 3.0
* VEGA56/64 or Radeon VII

動作環境の詳細などは, 

ROCm3.0（RadeonOpenCompute)の対応CPUやGPU、OSについて
https://qiita.com/T_keigo_wwk/items/c9cb42663afeed1838d9

を参照ください.

## 準備

ROCm オフィシャルの pytorch 関連のドキュメント, たとえば

https://rocm-documentation.readthedocs.io/en/latest/Deep_learning/Deep-learning.html

は**古くて正しくありません!! 無視してください**.

ROCm-Pytorch-Dockerの環境構築がいつの間にか楽になってた
https://qiita.com/T_keigo_wwk/items/e87c6e3b11cfe67785b6

Docker の rocm/pytorch image を引けば, libtorch_hip.so などすでにビルド済みのものが得られます(ソースコードから pytorch ビルドは不要) 

2020 年 1 月 15 日時点の最新である,

rocm/pytorch:rocm3.0_ubuntu16.04_py3.6_pytorch
https://hub.docker.com/layers/rocm/pytorch/rocm3.0_ubuntu16.04_py3.6_pytorch/images/sha256-74da06d57244ada641004a0b7fd880e353e26149f53acc21c6d2e1dc0c2108ac

を使います. 3.4 GB くらいある Docker image です.

Python で import torch できるのも確認しました

### python で import torch

場所としては `~/pytorch/build/lib.linux-x86_64-3.6` にあります.
python を使う場合は, `~/pytorch/build/lib.linux-x86_64-3.6` を PYTHONPATH あたりに設定して import でパス(`~/pytorch/build/lib.linux-x86_64-3.6/torch`)が見つかるようにしておきます.

うまく動作していれば

```
import torch
print(torch.cuda.is_available())
```
で True になるはずです. 
(この道のツウになると, ROCm 動作だと import torch で 3~4 秒くらい待つのがわかっているので, それできちんと ROCm を認識しているかわかります)

torch.cuda.get_devie_name(0) で GPU 名取得すると, `Device 687f' などが出力されるので, これで ROCm で動作しているのを確認することもできます(きちんとした名称を返してほしいところではあるが)

### ビルド物

ただ, docker image にあるのは, python setup install, cmake install 前の状態のようです.
`/root/pytorch/build` にビルド物や TorchConfig.cmake がありますが, 

https://pytorch.org/cppdocs/

を参考に, `CMAKE_PREFIX_PATH`(or `Torch_DIR`)でこの .cmake の場所を指定しても, util.cmake が存在しないなどでエラーになります.

PyTorch website にある build artifact はどう生成したのか不明です. Docker image 内で make install などすると, 再度ビルドが走ってしまいます.

とりあえずは header file や, `.so` に直接パスを指定して C++ アプリをビルドすることにします.

## 最小のサンプル

https://github.com/pytorch/pytorch/issues/30076
を参考に最小のコードを用意します.

```
#include <torch/torch.h>

#include <iostream>

int main()
{

    std::cout << torch::cuda::is_available() << "\n";

    torch::Tensor a = torch::ones({ 2, 2 }, torch::requires_grad());
    torch::Tensor b = torch::randn({ 2, 2 });
    a.to(torch::kCUDA);
    b.to(torch::kCUDA);
    auto c = a + b;
    return 0;
}
```

`torch::cuda::is_available()` で, CUDA(ROCm)のチェックができます. `to(torch::kCUDA)` で GPU 転送します(CPU backend だと seg fault する)

以下のような Makefile をでっちあげます.

```
INC_DIR=-I/root/pytorch/build/lib.linux-x86_64-3.6/torch/include/
INC_DIR += -I/root/pytorch/build/lib.linux-x86_64-3.6/torch/include/torch/csrc/api/include

LIB_DIR += -L/root/pytorch/build/lib.linux-x86_64-3.6/torch/lib

LIBNAMES= -ltorch_hip -ltorch_cpu -lc10 -lc10_hip

all:
        g++ $(INC_DIR) main.cc $(LIB_DIR) $(LIBNAMES)
``` 

あとは, `/root/pytorch/build/lib.linux-x86_64-3.6/torch/lib` に `LD_LIBRARY_PATH` を通して実行すれば,

```
1
```

と,  `is_available` が true である + `a.to(torch::kCUDA)` で seg fault しないはずです!

うまく GPU が detect できない場合は, docker で GPU を passthrough(?) する起動オプション(`--privileged --rm --device=/dev/kfd --device=/dev/dri --group-add video`を指定しているか, Docker 内部で `/opt/rocm/bin/rocminfo` で情報が出るか確認してみください.

### CUDA or CPU で実行する

ちなみに, CPU だけの場合は `-ltorch_cpu -lc10` だけリンクすればいけます. CPU 実行だと, `to(torch::kCUDA)` で seg fault するのを確認できると思います.

CUDA の場合は `-ltorch_cuda` などと, `_hip` の suffix を `_cuda` に置き換えればいけるはず...?

## まとめ

とりあえず動くのを確認できた段階ですが, C++ で完結できるのはやはり楽です. 現状リンクもそんなに面倒ではありませんでした(cmake の TorchConfig.cmake は利用せずともいける. caffe2 が絡むともうちょっと面倒になるかな?)

PyTorch には, Python レイヤーも含めビルドやパッケージング(CI)のドキュメントがほとんど無いので, ソースコードみたりしないとわからないのが辛いでした.

## TODO

* [ ] 実際に CIFAR10, VGG あたりを C++ で記述し学習させて, ROCm でうまく動作するか確認する.
* [ ] Make install あたりなどして, PyTorch website で配布しているパッケージ(build artifact)と同じものが再現できるか確認する.
* [ ] PyTorch 1.4 の Distributed RPC が libtorch でも使えるか試す.