LoginSignup
3
5

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-21

ディープラーニングのできる機械学習フレームワークTorchをWindowsに入れようとしてみたメモ.
基本的にGitHubのWikiのWindowsのUsing VisualStudio manuallyを踏襲すればいいが,(2017.3.21時点では)いくつか引っかかる点がある.

以下,前提環境.

  • Windows10
  • CMake
  • Git
  • Visual Studio 2015 Professional
  • OpenBLAS
    • バイナリ版はLAPACK付くのでFortranコンパイラが無いと不可.
    • (2017.3.31追記)wikiにblas周りのインストラクションが追加されている模様.lapackとblasのバイナリ版が紹介されている.

※更新が早いので,この情報は古い可能性が高いです.

Torchをインストール

  1. luajit-rocksをインストール
    • どこかにgit cloneする
      mkdir C:\libs && cd C:\libs && git clone https://github.com/torch/luajit-rocks.git
  2. cmakeする
    • cmake-guiでok
    • 64bitじゃなくて無印(32bit)にしておく
      • 64bitでも行けるかもしれないけど,公式が64bit試してないって言ってるので怪しいので試してないです できるかもしれないけど,的なことは言ってる
      • 64bitいけます.
    • ビルドディレクトリは適当に 一つbuildを掘る感じでいいのでは(C:/libs/luajit-rocks/build)
    • CMAKE_INSTALL_PREFIXはgitのワーキングディレクトリ(C:/libs/luajit-rocks)でいいかも.ProgramFilesはやめておいた方が…
  3. ビルドする
    • Releaseビルドにするのを忘れない.
  4. 環境変数を設定する

    • だいたいwikiのとおりでいい.
    • (2017.3.21追記)torchのビルド中にtorchcwrap.luaが見つからないエラーを吐いていたので,カレントディレクトリも探すようにさせるためLUA_PATHに追加.
    PATH=C:\libs\luajit-rocks;%PATH%
    LUA_DEV=C:\libs\luajit-rocks
    LUA_PATH=C:\libs\luajit-rocks\?;C:\libs\luajit-rocks\?.lua;C:\libs\luajit-rocks\lua\?;C:\libs\luajit-rocks\lua\?.lua;C:\libs\luajit-rocks\lua\?\init.lua;?.lua
    LUA_CPATH=C:\libs\luajit-rocks\?.DLL;C:\libs\luajit-rocks\LIB\?.DLL;?.DLL
    
  5. Torchをgithubからダウンロード
    mkdir C:\libs\Torch && cd C:\libs\Torch && git clone https://github.com/torch/torch7.git

  6. LuaRocks用のCMakeのラッパーを作成

    • LuaRocksはnmakeをビルダーに使うが,cmakeはMakefileを作る設定になっておらず,VisualStudioのソリューションを作る設定になっている.そこを書き換える
    • C:/libs/luajit-rocks/cmake.cmd
    if %1 == -E  (
    cmake.exe %* 
    ) else (
    cmake.exe -G "NMake Makefiles" -DCMAKE_LINK_FLAGS:implib=libluajit.lib -DLUALIB=libluajit %*
    )
    
  7. TorchのLuaRocksの設定ファイル(rockspec)をダウンロード
    C:\libs\Torchでluarocks download torch

    • ちなみにluajit-rocksを使わず本家のLuaRocksを使った場合,torchが見つからない.デフォルトのリポジトリには登録されていないのだ.torchの登録されている,別の検索リポジトリを追加する必要がある.やり方は暇な時にやる.あるいはおとなしくluajit-rocksを使う.
  8. ダウンロードした設定ファイルをちょっと変更.BLASの設定回り.

    • && $(MAKE)の前に以下を追加.
      -DBLAS_LIBRARIES=(openblasのライブラリのパス)/libopenblas.lib -DBLAS_INFO=open
    • OpenBLASじゃない場合はBLAS_INFOに他のキーワードを入れる.備考にて.
  9. VisualStudioの実行環境を整える

    • 各Visual Studioのバージョンに対応したvcvarsallを使えばたぶんいける
    • call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
  10. CMakeのラッパーにパスを通す

    • コマンドプロンプト上でパスを通す.PATH=C:\libs\luajit-rocks;%PATH%
    • パスは必ずluarocks makeの直前にやること.PATHの先頭に置くのがコツ.cmake.exeが代わりに呼ばれてしまうのを防ぐ必要があるため.
  11. 1回make

    cd C:\libs\Torch\torch7
    luarocks make ..\torch-scm-1.rockspec
    
    • 途中でエラーが出るかもしれない.高速化に使用できるフレームワークのチェック時に,非対応時にエラーを吐くことがある.その都度中止を押してスルーすればよい.
    • (2017.3.31追記) Unknown CMake command "check_function_exists" と出ることがある.CMakeのバージョンとlib/TH/cmake/FindLAPACK.cmakeの書き方の問題?C:\libs\Torch\torch7\lib\TH\cmake\FindLAPACK.cmakeの28行目include(CheckFortranFunctionExists)の下に,include(CheckFunctionExists)を追加.
  12. (エラーが出た場合)C:\libs\Torch\torch7\build\CMakeCache.txtをいじる

    • CMAKE_C_FLAGSとCMAKE_CXX_FLAGSの末尾に/arch:AVXを追加
    • CMAKE中にAVX2まわりがSuccessだったら代わりに/arch:AVX2を追加すればよい?なったことが無いからわからない
    • CMakeCache.txt中のCXX_AVX_FOUNDでも有効かどうか確認できる
      • 本家は対応しなさそうだなー
  13. (エラーが出た場合)もう一回make
    luarocks make ..\torch-scm-1.rockspec

    • これでビルドが成功すればLuaRocksに登録される.
  14. テストを起動してみる
    luajit -ltorch -e "torch.test()"

    • LAPACKを入れていない場合,LAPACKまわりでERRORを検出する模様.LAPACKまわりの機能を使うものがあれば,LAPACK入りビルドが必要になる.使わないなら気にする必要はない.

その他モジュールをインストール

luaffi

wikiどおり.

nn

基本はwikiどおり.
nnでコンパイルエラーする場合,
nn\lib\thnn\generic/FeatureLPPooling.c(213): error C3015: OpenMP 'for' ステートメントの初期化には、正しくない形式が含まれていますなどと出る場合,該当ファイルのOpenMPにかかわる部分を修正する必要がある(参考記事).
OpenMPの部分だけ,以下のように変更する.

  • for文の中で変数定義しない
  • ループに使うインデックス変数は符号付整数型にする

すなわち,以下のようにする.

void
THNN_(FeatureLPPooling_updateOutput)(
  THNNState *state,
  THTensor *input,
  THTensor *output,
  accreal power,
  int width,
  int stride,
  bool batchMode) {
  int inputDim = THTensor_(nDimension)(input);
    int batch;
    int opt1;
    int opt2;
    int outputFeature;
    int _i;

  if (batchMode) {
    THArgCheck(inputDim >= 2 && inputDim <= 4, 2,
               "input must be 2-4 dimensions for batch mode");
  } else {
    THArgCheck(inputDim >= 1 && inputDim <= 3, 2,
               "input must be 1-3 dimensions for non-batch mode");
  }

  FeatureLPPoolingSizes inputDesc =
    THNN_(FeatureLPPooling_upcastCPU)(input, batchMode);

  // Make sure the feature dimension is properly sized
  THArgCheck(inputDesc.size[1] >= width, 3,
             "input: feature dimension must be >= width");

  // Make sure that width and stride are within range
  THArgCheck(width >= 2 && width <= 16, 5,
             "width must be between 2 - 16");

  THArgCheck(stride >= 1 && stride <= 4, 6,
             "stride must be between 1 - 4");

  // Resize output

  THNN_(FeatureLPPooling_resizeForOutputCPU)(
    output, input, batchMode, width, stride);

  FeatureLPPoolingSizes outputDesc =
    THNN_(FeatureLPPooling_upcastCPU)(output, batchMode);

  real* inputP = THTensor_(data)(input);
  real* outputP = THTensor_(data)(output);

#pragma omp parallel for
  for (batch = 0; batch < inputDesc.size[0]; ++batch) {
#pragma omp parallel for
    for (opt1 = 0; opt1 < inputDesc.size[2]; ++opt1) {
#pragma omp parallel for
      for (opt2 = 0; opt2 < inputDesc.size[3]; ++opt2) {
#pragma omp parallel for
        for (outputFeature = 0;
             outputFeature < outputDesc.size[1]; ++outputFeature) {

          accreal v = (accreal) 0;
#pragma omp parallel for
          for (_i = 0; _i < width; ++_i) {
            size_t inputFeature = outputFeature * stride + _i;
            if (inputFeature >= inputDesc.size[1]) {
              break;
            }

            v +=
              pow(inputP[flpGetOffset(&inputDesc,
                                      batch,
                                      inputFeature,
                                      opt1,
                                      opt2)], power);
          }

          outputP[flpGetOffset(&outputDesc, batch, outputFeature, opt1, opt2)] =
            pow(v, (accreal) 1 / power);
        }
      }
    }
  }
}
void
THNN_(FeatureLPPooling_updateGradInput)(
  THNNState *state,
  THTensor* gradOutput,
  THTensor* input,
  THTensor* output,
  THTensor* gradInput,
  accreal power,
  int width,
  int stride,
  bool batchMode) {
  int inputDim = THTensor_(nDimension)(input);
    int batch;
    int opt1;
    int opt2;
    int outputFeature;
    int _i;

  if (batchMode) {
    THArgCheck(inputDim >= 2 && inputDim <= 4, 3,
               "input must be 2-4 dimensions for batch mode");
  } else {
    THArgCheck(inputDim >= 1 && inputDim <= 3, 3,
               "input must be 1-3 dimensions for non-batch mode");
  }

  FeatureLPPoolingSizes inputDesc =
    THNN_(FeatureLPPooling_upcastCPU)(input, batchMode);
  FeatureLPPoolingSizes gradOutputDesc =
    THNN_(FeatureLPPooling_upcastCPU)(gradOutput, batchMode);
  FeatureLPPoolingSizes outputDesc =
    THNN_(FeatureLPPooling_upcastCPU)(output, batchMode);

  // Make sure the feature dimension is properly sized
  THArgCheck(inputDesc.size[1] >= width, 3,
             "input: feature dimension must be >= width");

  // Make sure that width and stride are within range
  THArgCheck(width >= 2 && width <= 16, 7,
             "width must be between 2 - 16");

  THArgCheck(stride >= 1 && stride <= 4, 8,
             "stride must be between 1 - 4");

  for (int i = 0; i < 4; ++i) {
    THAssertMsg(outputDesc.size[i] == gradOutputDesc.size[i],
                "output and gradOutput sizes do not match");
  }

  // Make sure that the input sizes produce the output sizes
  THArgCheck(flpOutputSize(inputDesc.size[1], width, stride) ==
             outputDesc.size[1], 3,
             "input and output sizes do not match with respect to "
             "width and stride");

  // Resize `gradInput` based on `input`
  THNN_(FeatureLPPooling_resizeCPU)(gradInput, input);

  // Zero gradInput for accumulation
  THTensor_(zero)(gradInput);

  FeatureLPPoolingSizes gradInputDesc =
    THNN_(FeatureLPPooling_upcastCPU)(gradInput, batchMode);

  real* gradOutputP = THTensor_(data)(gradOutput);
  real* gradInputP = THTensor_(data)(gradInput);
  real* outputP = THTensor_(data)(output);
  real* inputP = THTensor_(data)(input);

#pragma omp parallel for
  for (batch = 0; batch < inputDesc.size[0]; ++batch) {
#pragma omp parallel for
    for (opt1 = 0; opt1 < inputDesc.size[2]; ++opt1) {
#pragma omp parallel for
      for (opt2 = 0; opt2 < inputDesc.size[3]; ++opt2) {
#pragma omp parallel for
        for (outputFeature = 0;
             outputFeature < outputDesc.size[1]; ++outputFeature) {

          // Load output (f(x_is)). It is possible that this is zero, in
          // which case we'll ignore this point.
          real outputV =
            outputP[
              flpGetOffset(&outputDesc, batch, outputFeature, opt1, opt2)];

          if (outputV == (real) 0) {
            continue;
          }

#pragma omp parallel for
          for (_i = 0; _i < width; ++_i) {
            size_t inputFeature = outputFeature * stride + _i;
            THAssert(inputFeature < inputDesc.size[1]);

            real gradOutputV =
              gradOutputP[
                flpGetOffset(&gradOutputDesc, batch, outputFeature, opt1, opt2)];
            real inputV =
              inputP[
                flpGetOffset(&inputDesc, batch, inputFeature, opt1, opt2)];

            // Calculate grad * (x_i / f(x_is))^(p - 1)
            real v = gradOutputV * pow(inputV / outputV, power - (accreal) 1);

            gradInputP[
              flpGetOffset(&gradInputDesc, batch, inputFeature, opt1, opt2)]
              += v;
          }
        }
      }
    }
  }
}

image

zlib, ligpng, libjpegが必要.現在のGnuWin32付属のzlib, libpng, libjpegを使おうとするとエラーする.自分でソースを拾ってビルドする必要がある.libjpegはlibjpeg-turboを使うのが楽で確実.DLLにはパスを通すこと.

  1. いろいろ準備
    • zlib, libjpeg, libpngをビルド
      • libjpeg-turboを使うのであれば,すべてCMake.staticでいい.
      • sharedを使うならdllパスを通す.
      • vcpkgを使うと楽.
  2. ビルド環境を整える

    cd libs/Torch
    luarocks download image
    git clone https://github.io/torch/image.git
    cd image
    luarocks make ..\(ダウンロードしたrockspecファイル)
    
  3. build/CMakeCache.txtから,ZLIB,PNG,JPEGのINCLUDE_DIR,LIBRARY_RELEASEの設定の編集をする

    • 設定の編集だけならcmake-guiを使うと楽かも.
  4. もう一度luarocks make ..\image-scm1.0.rockspec

  5. luajit -limage -e "image.test()"

    • Compressまわりでエラーするかも.解決方法はわからないが,とりあえずそこらへんを触らない使い方をすれば使えると思う
    • jpegの方はjpeg8対応ビルドにするとOK.

trepl

要readline.GnuWinのreadlineか,WinEditLineが必要.
※ (2017.9.17) 最近はtreplパッケージの中でreadlineを用意しているようだが,luajit-rocksのビルド時のものと別のものになってしまうと,luajit-rocksがエラーで起動できなくなってつらい.やってしまったら,trepl,luajit-rocksの中のreadline.dllを削除して,rockspecの中からreadline関係をコメントアウトしてreadline.dllを生成しないようにすることで,上で用意したreadline.dllを使用してもらうようにする.

  • GnuWinのreadlineの場合

    1. 32bitならgnuwin32,64bitならこれを64bitでビルド
    2. 成果物をrockspecのincdirs, libdirs, librariesに書き込んで,definesの行を消して,メイク.こんなかんじ

      windows = {
          modules = {
              ['readline'] = {
                  sources = {'readline.c'},
                  libraries = {'readline'},
                  -- defines = {"WinEditLine"},
                  incdirs = {"C:\\gnuwin64\\include"},
                  libdirs = {"C:\\gnuwin64\\lib"},
                  -- libraries = {'edit_static', 'user32'}
                  libraries = {'readline'}
              }
          }
      }
      
    3. 成果物のreadline.dllを,luajit-rocksのreadline.dllと置き換える.

  • WinEditLineの場合
    1. ここからダウンロード&cmakeでビルド
    2. 成果物をrockspecのincdirs, libdirsに書き込んでメイク.
    3. 成果物のreadline.dllを,luajit-rocksのreadline.dllと置き換える.

lua-cjson

  1. cd C:\libs\Torch && luarocks download --rockspec lua-cjson
  2. ダウンロードしたrockspecの中の,sourceのurlから,ソースをダウンロード,展開
    • ここではC:\libs\Torch\lua-cjsonにしておく
  3. lua_cjson.cの77行目あたりの空いているところに以下を追加

    #if defined(_WIN32) && !defined(strncasecmp)
    #define strncasecmp(x,y,z) _strnicmp(x,y,z)
    #endif
    
  4. cd lua-cjson && luarocks make ..\(ダウンロードしたrockspecファイル)

threads

  1. dlfcn-winをインストール
    1. git clone https://github.com/dlfcn-win32/dlfcn-win32.git
    2. visual-studio/12にあるプロジェクトを使って,dlをビルド
    3. dl.dll, dl.lib, dlfcn.hを適当な場所に置く
      • ここではC:\libs\dlfcn-winに置くことにする
  2. git clone https://github.com/torch/threads.git
  3. threads/rocks/threads-scm-1.rockspecに書いてあるコメントを参考に,makeを実行
    • luarocks make rocks\threads-scm-1.rockspec WIN_DLFCN_INCDIR="C:\libs\dlfcn-win" WIN_DLFCN_LIBDIR="C:\libs\dlfcn-win"

cutorch, cunn

nmake対応にするため,rockspecの中の,$(MAKE)の後ろの-jオプションを消す.
残念ながらnmakeには並列コンパイル機能がついていない.clのコンパイラオプションに/MPを仕込む必要がある.

cutorch (2017.9.17追加)

  1. lib/THC/THCReduce.cuhでエラーすることがある.
    THC_getGridFromTiles(THCCeilDiv(outElements, (long)block.x), grid);THC_getGridFromTiles(THCCeilDiv((long)outElements, (long)block.x), grid);に変更し,型を統一する.
  2. lib/THC/generic/THCTensor.cでエラーすることがある.
    VisualStudio的には配列のサイズ指定に変数を使用するのはご法度なので,ちゃんとmallocを使うようにする.すなわち,long *op_sizes[count];long op_dims[count];long **op_sizes = malloc(sizeof(long *)*count);long *op_dims=malloc(sizeof(long)*count);にして,関数の末尾にfree(op_sizes);free(op_dims);をつける.

cunn (2017.9.17追加)

cunn\lib\THCUNN\LogSigmoid.cu が以下のエラーすることがある.
more than one instance of overloaded function "fmaxType" matches the argument list
複数あるconst T max = fmaxType(0.f, - *input);を探し出し,const T max = fmaxType((T)0, - *input);に変更する.

cudnn (2017.9.17追加)

cuDNN本家は先にインストールしてある必要がある.

luarocks download --rockspec cudnn
git clone git://github.com/soumith/cudnn.torch.git
cd cudnn.torch

その後,..\cudnn-scm-1.rockspecを編集し,build_commandの&& $(MAKE)の前でcuDNNをいれた場所を指定する.例えば,-DCUDNN_LIBRARY="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v8.0/lib/x64/cudnn.lib"
なお,本家cuDNNのバージョンに合わせて,gitリポジトリのブランチを変更してやる必要がある.cuDNN v6をインストールした場合は,git checkout R6をやる.対応しているかどうかはgit branchで確認.
その後,luarocks make ..\cudnn-scm-1.rockspecでインストール.

graph

lua上でのgraph自体のインストールは問題ないが,64bitの場合,64bit対応済みGraphVizにする必要がある.
GraphViz公式は64bit対応する気がないようなので,サードパーティーの64bitパッチをあてる. https://github.com/mahkoCosmo/GraphViz_x64
そして,環境変数PATHにGraphViz/binへのパスを通す.
ただし,上パッチのzlibの参照がおかしい(zlibのバージョンが違う?)ので,zlibを自分で用意してGraphViz/binにコピーしてあげる必要がある.

他のモジュール

(2017.9.17更新)
素直にインストールできないものが他にもいろいろありそう.素直にできるものもあると思うけど.
知ってる限り素直にいったもの一覧:
cwrap, dok, graphicsmagick, luafilesystem, optim, paths, penlight, sundown, sys, xlua
ダメだったもの一覧:
async, cuda-convnet2(ccn2)

BLASまわり (2017.9.17更新)

LAPACK,BLASによりテンソル計算回りを高速計算することができる.
BLASのみならOpenBLASを使うのが高速だが,LAPACKも生かしたい場合はVisualSutdioオンリーはあきらめたほうが良い.
LAPACKを使いたい場合は,LAPACK for Windowsを使うことになるが,ビルドにはMinGWあるいはVisualStudio + Intel Fortran Compiler(有料)が必要になる.
LAPACKに付属するのはRefBLASだが,RefBLASはOpenBLASに比べてかなり遅い.
できればOpenBLASを使いたいが,OpenBLAS対応のLAPACKバイナリは提供されていない.

最も簡単なインストールをする場合は,以下の手順がよい.

  1. MSYS2+MinGWをインストール
    筆者はchoco install msys2でインストールしている.dllのある場所へのパスが通っているのを確認する.
  2. LAPACK for Windowsの,Prebuilt dynamic libraries using Mingwの,RefBLASのdll,lib,LAPACKのdll,libをダウンロードし,適当な場所に置く.パスを通す.
  3. torchのインストールの時に,rockspecで指定するLAPACK,BLASに,上記を置いた場所を指定.
  4. インストール後,torch.test()を試して,すべての機能が使えることを確認する.

GPU対応

インストール

CUDA,cudnnがインストールされていることが前提.
cutorch, cunn, cudnnをインストール.
ビルドに時間がかかるので注意.

GPU版のチェックポイントをCPU版に変換

GPUで諸々作った場合,実際に使うときはCPUで使うことも多いと思うので,CPU版に変換する方法.
GPU入ったマシンでやる必要があります.

require 'torch'
require 'nn'
require 'nngraph'
require 'cutorch'
require 'cudnn'
torch.setdefaulttensortype('torch.FloatTensor')
checkpoint=torch.load('filename.t7')
checkpoint:float() --CudaTensor to FloatTensor
cudnn.convert(checkpoint, nn) --CudaLayer to nnLayer
torch.save('filename_cpu.t7',checkpoint)

備考

  • Lua: プログラミング言語およびインタプリタ実行環境.chocolateyにのってる.
  • LuaJit: Luaより高速な実行環境.かなりはやくなるらしい.chocolateyにのってない.
  • LuaRocks: Luaのパッケージ管理ツール.パッケージインストールには,だいたいCMakeとC++コンパイラが必要.あとwindowsで使うにはちょっとしたカスタマイズが必要.
  • luajit-rocks: ↑3つが全部乗ってる.しかもtorch仕様で,torch対応のluarocksリポジトリがデフォルトで設定される.LuaJITを使うか,Luaを使うか,更にどのバージョンを使うかをCMake時に選べるので,基本的にこれ1本でいいっぽい.ちなみにLuaJITにtorch印がつく.
  • Using MSVC automaticallyはインストール失敗してしまった.たぶんBLASまわり?

なんかへん

  • 反映されてない気がする
    キャッシュの影響かもしれない.CMakeCache.txtを消してまたやり直してみるとか,buildディレクトリごと消してやりなおしてみるとかするといいかもしれない.
  • require('torch')でエラーする
  • torch.loadでtable index is nilとかいわれる
  • (私的TODO)
    • luajit-rocks以下のsharedの場所が意図しない位置にできる?
  • おそい
    調査中…
  • mt.exe : general error c101008d: Failed to write the updated manifest to the resource of fileとかでる
    • ウイルスセキュリティのせいなことが多いらしい.(参考
  • Error: Failed move: could not delete
    • おそらく同上.
  • 大きなバッチサイズをforwardすると落ちる
    • 調査中…
3
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5