Caffe2をとにかくWindows+VisualStudioでビルド

2018.9.28に，連携を意識するためか，PyTorchのリポジトリにCaffe2が引っ越しました．
また，Windows+VisualStudioにおけるビルドも大きく改善しているようです．

以下，参考までに過去記事です．

---

深層学習（CNN）フレームワークCaffe2をWindowsに入れてみたメモ．
2017.11.14時点では，本家のインストール情報ではWindows+VisualStudioでのビルドが行き詰まり，GPU利用ができません．それをできるように頑張ったメモです．

本記事の時点では全く枯れておらず，更新がもりもり進んでおり，開発はGCCで進んでいるものと思われますので，**最新コミットはまずVisualStudio対応していない**と思ったほうがよさそうです．ちょくちょくWindows対応をしたコミットが見られるので，なるべく最新版をと思う場合はそのコミットを探してチェックアウトすることになりそうです．
また，本家はVisualStudio2017でメンテすると言っているようなので，本記事で採用しているVS2015のような旧版ではC++言語仕様準拠が遅れている機能を使われることもあると思います．

以下，本記事での環境．
＃TODO:コミットID 3d3a78af0a9a2b300c58ee4450e009fc41362d95の場合で書く

Caffe2 (gitコミットID: 646c316288d77f43b7228111c8d6dfd4348e7647)
Windows10
CMake 3.9.1
Visual Studio 2015 Professional

※この情報は古い可能性があります．既にバグフィックスされている可能性があります．

# 事前準備編
[pthreads for Windows](https://sourceforge.net/projects/pthreads4w/)が必要なのでビルドしておく．[vcpkg](https://docs.microsoft.com/ja-jp/cpp/vcpkg)が便利です．

# CMake編
* [GitHub](https://github.com/caffe2/caffe2)から一式をダウンロードする．展開したディレクトリ上で作業する．
* 事前にbuild_host_protocをビルドしておく．./scripts/build_host_protoc.batが機能する．最新コミットではVSのバージョンが異なるかもしれないので適当に変更する．CMakeには事前にパスを通しておくこと．既にprotoc.exeを持っていればそれでも大丈夫です．
* CMake GUIを起動．ソースディレクトリは./，ビルドディレクトリは本記事では./buildとしておく．
* BLASはEIGENを使用．他は未調査．
* PROTOBUF_PROTOC_EXECUTABLEを定義すること．build_host_protocでビルドしたprotoc.exeを指定すればよい．
* ソース中にExport情報が書き込まれていないのでDLLビルド対応していないらしい．BUILD_SHARED_LIBSはOFF
* BUILD_PYTHONをON，Python27を見ていなかったら手動でパス設定する．Python27オンリー．Python3は無理．
    * TODO: [Issue](https://github.com/caffe2/caffe2/issues/315)にて、2017年12月時点でPython3対応完了確認とのこと。要調査。
* CMAKE_INSTALL_PREFIXを適当に設定する．本記事では./installedとしておく．
* CUDA_ARCH_NAMEはGPUと相談しながら適当なものを選ぶ．Allにしておくと，CUDA8.0の場合，sm_20を読んでしまってエラーする．
* USE
    * CUDA，MPI，OPENMP，THREADSをON，それ以外はOFF
    * LEVELDBなどは使いたいところだが，可否は未調査．

# ファイル編集編
いくつかのファイルを編集する必要がある．
./build/Caffe2.slnを開いて，Releaseにしてから作業する．終わったらINSTALLをビルド．

* ./caffe2/operators/batch_matmul_op.cc
直りました．（コミットID: 3d3a78af0a9a2b300c58ee4450e009fc41362d95）
int64からintへの変換が暗黙的で怒られるので，38行目のin[0].dims(0)をintにキャストする．
``(int)(in[0].dims(0)), a_dim0, b_dim1},``

* ./caffe2/utils/threadpool/WorkersPool.h
おおよそ直りました．（コミットID: 3d3a78af0a9a2b300c58ee4450e009fc41362d95）
asm volatileで怒られる．このフレーズはインラインにアセンブラ指令を書いているところで，gcc用の表現であり，VSでは書き方が異なる．
http://d.hatena.ne.jp/kikairoya/20100220/1266668559
https://stackoverflow.com/questions/31601057/is-there-any-example-that-triple-colon-is-a-valid-syntax-in-c-code
VSだと\_\_asmブロックの中に書いたり，該当の命令に置き換えたりする必要がある．
また，pthreadを用いる記述になっているため，pthreads for Windowsを利用して対応させる必要がある．
    * 3行目を以下に変更．
    
    ```
    #if defined(_MSC_VER)
    #include <pthread.h>
    #include <malloc.h>
    #else
    #include <thread>
    #endif
    ```

    * 27行目を以下に変更．
    
    ```
    #if defined(_MSC_VER)
        p = _aligned_malloc(kGEMMLOWPCacheLineSize, sizeof(T));
    #elif !defined(__ANDROID__)
    ```

    * 75行目を以下に変更．
    
    ```
    #if defined(_MSC_VER)
    #define GEMMLOWP_NOP __nop;
    #else
    #define GEMMLOWP_NOP "nop\n"
    #endif
    ```

    * 83行目を以下に変更．
    
    ```
    #if defined(_MSC_VER)
        GEMMLOWP_NOP64
    #else
        asm volatile(GEMMLOWP_NOP64);
    #endif
    ```


    * 95行目を以下に変更．
    
    ```
    #if defined(_MSC_VER)
        _WriteBarrier();
    #elif GEMMLOWP_ARM_32
    ```

    * 105行目を以下に変更．
    
    ```
    #if defined(_MSC_VER)
        _ReadBarrier();
    #elif GEMMLOWP_ARM_32
    ```

    * CAFFE2_CPUプロジェクトの設定を編集する．pthreadsへのインクルードパスの追加，およびビルドしたpthreadsVC2.libへのリンクおよびパスの追加をする．
    * CAFFE2_GPUプロジェクトの設定を編集する．pthreadsVC2.libへのリンクおよびパスの追加をする．

* .\caffe2\operators\layer_norm_op.cu
255-256行のint64->intの代入でおこられる．以下に書き換える．
    
```
const unsigned long long left = norm_inputs.size_to_dim(canonical_axis);
const unsigned long long right = norm_inputs.size_from_dim(canonical_axis);
```

* .\caffe2\operators\pool_op_cudnn.cu  
IN,OUTのenumがVSで定義済みと言われる．仕方ないのでその前でundefする．
インクルードの下あたりに以下を追加．

```
#if defined(_MSC_VER)
#undef IN
#undef OUT
#endif
```

* .\caffe2\utils\GpuBitonicSort.cuh  
caffe2/utils/math.hのintegerIsPowerOf2がVisualStdioだとconstexprでどうしても書けないので仕方なくマクロ関数にしたよというコメントがある．
マクロ化の影響で，GpuBitonicSort.cuhのintegerIsPowerOf2を使用しているところで，ネームスペースが付いていると怒られる．
仕方ないのでVSの場合にネームスペースつけないようにする．他でintegerIsPowerOf2が定義されないことを祈る．  
42-45行目を以下に変更．

```
#if defined(_MSC_VER)
  static_assert(integerIsPowerOf2(Power2SortSize),
                "sort size must be power of 2");
  static_assert(integerIsPowerOf2(ThreadsPerBlock),
                "threads in block must be power of 2");
#else
  static_assert(math::integerIsPowerOf2(Power2SortSize),
	  "sort size must be power of 2");
  static_assert(math::integerIsPowerOf2(ThreadsPerBlock),
	  "threads in block must be power of 2");
#endif
```

* .\caffe2\operators\recurrent_op_cudnn.cc  
\#define中に#ifを入れ子で変なことになっている模様．インラインにしないで，#ifで別々に分けて書くことに．  
118-132行目を以下に変更．

```
#if CUDNN_MAJOR >= 7
	  CUDNN_ENFORCE(cudnnSetRNNDescriptor(
		  cudnn_wrapper_.inline_cudnn_handle(),
		  rnnDesc_,
		  hiddenSize,
		  numLayers,
		  dropoutDesc_,
		  rnnInput,
		  rnnDirection,
		  rnnMode,
		  CUDNN_RNN_ALGO_STANDARD, // TODO: verify correctness / efficiency.
		  cudnnTypeWrapper<T>::type));
#else
	  CUDNN_ENFORCE(cudnnSetRNNDescriptor(
		  rnnDesc_,
		  hiddenSize,
		  numLayers,
		  dropoutDesc_,
		  rnnInput,
		  rnnDirection,
		  rnnMode,
		  cudnnTypeWrapper<T>::type));
#endif
```

# Pythonで動かしてみる
PYTHONPATHに，./buildを追加して，PATHに./build/caffe2/python/Releaseを追加して，インタプリタを起動し，``from caffe2.python import workspace``などとしてみる．後は[Caffe2公式チュートリアル](https://caffe2.ai/docs/tutorials)を見て作業してみるとCaffe2に浸れます．