PyTorchを使ったHoudiniノードを作る <C++/HDK>

:::note info
こちらは、以前（2021/01/10）に別サービスで展開した内容を Qiita に移行したものです。
:::

# はじめに

PyTorch を使った Houdini ノードの作成方法に関しては試したことがなかったので、それをテストしたので、そのまとめを書いておく。

## 開発環境

> Windows 10
> Houdini 18.5.460
> PyTorch 1.7.1 CUDA 11
> Visual Studio 2017

## サンプルコード

https://github.com/takavfx/MLHoudiniSamples/tree/master/ML0002_PyTorchPlugin

# 準備

## LibTorch をダウンロード

Houdini のノードとして C++ での実装をするにあたって、[LibTorch(PyTorch C++ Distribution)](https://pytorch.org/cppdocs/installing.html) を使用する。

[PyTorch のサイト](https://pytorch.org)にて、適切なバージョンをダウンロード。
そして任意のディレクトリに展開して配置しておく。

ここで注意すべきなのが、LibTorch は Rlease/Debug ビルド用のライブラリがそれぞれ別々になっている(NOT ABI-compatible)。なので、ビルドソリューションによって切り替える事。

![xx97fd2j5i843sm9m2m09nr5hazq.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/87ffa3d4-7ec2-1605-2afd-b177d77458bf.png)


# CMake

基本的な CMake は次のファイルで行う。

```cmake:CMakeLists.txt
cmake_minimum_required( VERSION 3.6 )

project( PyTorchTest )

# CMAKE_PREFIX_PATH は、Houdiniインストールパスの
# toolkit/cmakeのサブディレクトリパスを含める。
# 詳しくは、HDKドキュメントの"Compiling with CMake"セクションを参照。
list( APPEND CMAKE_PREFIX_PATH "$ENV{HFS}/toolkit/cmake" )

# HoudiniライブラリとLibTorchライブラリのヘッダーファイルがある場所を指定。
# そして、インポートしたライブラリをHoudiniとTorchとしてターゲットを命名。
find_package( Houdini REQUIRED )
find_package( Torch REQUIRED )

set( library_name SOP_PyTorchTest )
# LibTorch用にフラッグを受け取れる様に追記。
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}" )

# 埋め込み型DSファイルをSOP_PyTorchTest.Cの中に生成.
# この関数は、HoudiniConifg.cmake内に定義。
houdini_generate_proto_headers( FILES SOP_PyTorchTest.C )

# ソースファイルを追加。
add_library( ${library_name} SHARED
    SOP_PyTorchTest.C
    SOP_PyTorchTest.h
)

# HoudiniライブラリとLibTorchへのライブラリをライブラリネームとしてリンク。
# また、必要インクルードディレクトリとコンパイル定義を追加。
target_link_libraries( ${library_name} Houdini "${TORCH_LIBRARIES}")

set_property(TARGET ${library_name} PROPERTY CXX_STANDARD 14)

# Include ${CMAKE_CURRENT_BINARY_DIR} for the generated header.
target_include_directories( ${library_name} PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}
)

# ライブラリの出力ディレクトリ(デフォルトでは、
# `C:\Users\<username>\Documents\houdini<version>\dso`)など、
# いくつかの共通ターゲットプロパティを設定。
# この関数は、HoudiniConifg.cmake内に定義。
houdini_configure_target( ${library_name} )

# Visual Studioでのビルドの場合に有効
# ポストビルドコマンドとして、リンクされたファイルを
# ビルドターゲットディレクトリにコピーする。
# コピー前にはbinディレクトリを追加し、LibTorch関連はそちらにコピー
if (MSVC)
    set(binary_dir $<TARGET_FILE_DIR:${library_name}>/../bin)
    add_custom_command(TARGET ${library_name}
                        COMMAND ${CMAKE_COMMAND} -E make_directory
                        ${binary_dir})
    
    file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll")
    add_custom_command(TARGET ${library_name}
                        POST_BUILD
                        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        ${TORCH_DLLS}
                        ${binary_dir})
endif (MSVC)
```

## テスト用のコード編集

今回は、SOP_Star のサンプルソースをもとにテストした。
テストするにあたって、次の項目を書き換えた。

```cpp:SOP_PyTorchTest.C
#include <torch/torch.h>

// ~ 省略 ~

// ノードがクックされた際の処理
void
SOP_PyTorchTestVerb::cook(const SOP_NodeVerb::CookParms &cookparms) const
{
    // ~ 省略 ~
    torch::Tensor tensor = torch::rand({2, 3});
    std::cout << tensor << std::endl;
    // ~ 省略 ~
}
```

## ビルド

Command Line Tools を起動して、作業ディレクトリに移動する。
Command Line Tools を用いなくてもいいが、各種環境設定などなどが面倒なので、おとなしくそれを使った方が吉。

```batch
mkdir build
cd build
cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_PREFIX_PATH=\path\to\torchlib
```

そしてビルド。

```batch
cmake --buld . --config Release
```

:::note warn
この時点でエラーが出る場合、以下のトラブルシュートの欄を参照。
場合によっては、Visual Studio を起動してから、各種問題を解決してからビルドすると良い。
:::

# Houdini上でテスト

始めに bin ディレクトリに対し、`set PATH=\path\to\bin` (デフォルトでは、`C:\Users\<username>\Documents\houdini18.5` 以下)と環境変数をセットする。

Houdini を起動し、/obj/geo1 と作成し、ノードを作成してみる。
次の様に出力されればビルド成功。

![tc3q03xsenpyz5aw0hyrq59aibqa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/7e70b598-456c-d9cc-e09b-584ff424c96e.png)



# トラブルシュート

以下、ビルドしていた際に起きたトラブルで対処したもの。

## C2061: Syntax Error: identifier 'IValue'

### 原因

*libtorch\include\ATen/core/ivalue.h*の中の`IValue`の前の行にある Doxygen 用の部分がうまく機能しておらず、それがVisual Studio のコンパイラに引っかかっている。

https://github.com/pytorch/pytorch/issues/24308#issuecomment-578306016

### 解決方法

エラーが起きてる以下の箇所を削除。
使う側としては、Doxygen でドキュメント化するわけでもないので、削除対応を選択した。

```cpp
/// \cond DOXYGEN_CANNOT_HANDLE_CONSTRUCTORS_WITH_MACROS_SO_EXCLUDE_THIS_LINE_FROM_DOXYGEN
C10_DEPRECATED_MESSAGE("IValues based on std::unordered_map<K, V> are slow and deprecated. Please use c10::Dict<K, V> instead.")
/// \endcond
```

---

## LINK : fatal error LNK1104: cannot open file 'torch-NOTFOUND.obj'

### 原因
ビルドソリューション用のライブラリを選択ミスしている。
このエラーの場合は、Release ビルド用のライブラリをダウンロードしてきて参照しているが、この torch-NOTFOUND.obj ファイルが含まれるのは Debug ビルド用のもの。

https://github.com/pytorch/pytorch/issues/24305

### 解決方法
適切(Release ビルド用ライブラリ)なビルド用ライブラリをダウンロードしてきて使用する。

---

## C2872: "std":ambiguous symbol エラー (Release)

![ovbdq2xhpkswj5562tjoctoiw0ec.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/2217c528-f569-cdaa-f536-e3da25c7eb9f.png)


### 原因

`Conformance mode` が `Yes` になっている。
Visual Studio 2017 以降で追加されたもので、非標準の C++ 構文は認めないというものとのこと。LibTorch 側がこれに引っかかるので、ここはどうしようもないので切り替える事で対応。強制的に `No` にする方法ない…？？

https://github.com/pytorch/pytorch/issues/18607

### 解決方法

`Conformance mode` を `No` に設定する。

![4pfnq9tqjwtzfj18k0nhhaxl9gzd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/3247246d-9a62-7008-d5ac-645892cdedf5.png)

---

## C2872: "std":ambiguous symbol エラー (Debug)

Debug でビルドしようとすると、上記解決策を試してみても修正されずにエラーが出続ける状態が続いく。

### 原因

各コード中の std が指定ビルドバージョンの構文に会っていないため。

### 解決方法

`std` となっているところをすべて `::std` と書き換える。

---

## LibTorchのdllが、Houdiniのバージョン認識にひっかかる

:::note warn
デフォルトの起動だとエラーも何も出力されず、ノードを Tab 検索しても出てこなかったりして、この問題に気づかないことが多い。そういった原因がわからない場合、とりあえず、`set HOUIDNI_DSO_ERROR=1` と環境変数をセットして、DSO 関連のエラーをすべて表示するようにする。
:::

具体的には次のエラーが起きていた。

![im8se45h15xg02sc9gv4figojzhy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51857/4b222eaa-5395-5636-0895-90b17248a21c.png)


### 原因

dso ディレクトリ以下には、Houdini が Houdini のプラグインとして認識するもののみを置くべきで、そこにサードパーティのものがあるがゆえに、Houdini のバージョン識別の処理が走った際に、その情報が取得できずに弾かれるという問題。

### 解決方法

サードパーティの dll 等は dso ディレクトリ以下に置かず、bin などをのディレクトリを別途つくってそこに配置。`set PATH=\path\to\bin` として設定することで解決できる。

:::note warn
こちらは、CMake 側で、ディレクトリを作成。そちらに LibTorch 関連のバイナリをコピーする仕組みにすることで対応。
:::

---

## bin ディレクトリが作成できない

LibTorch 関連のファイルを独自の bin ディレクトリにコピーしたいが、bin ディレクトリが存在しないとエラーで止まる。

### 原因

存在しないディレクトリにコピー処理をしようとしても、ディレクトリ自体を作る機能がコピーコマンドに存在しないので、予め作る必要がある。

### 解決方法

ビルド時のコピー前にフォルダを作る処理を入れる。

```cmake:CMakeLists.txt
if (MSVC)
    set(binary_dir $<TARGET_FILE_DIR:${library_name}>/../bin)
    add_custom_command(TARGET ${library_name}
                        COMMAND ${CMAKE_COMMAND} -E make_directory
                        ${binary_dir})
    
    file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll")
    add_custom_command(TARGET ${library_name}
                        POST_BUILD
                        COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        ${TORCH_DLLS}
                        ${binary_dir})
endif (MSVC)
```

# 最後に

こうして、とりあえず外部ライブラリの LibTorch を取り込むことができるので、これをベースに色々 Houdini の PyTorch を使った機械学習ノードを作っていけると思う。