DL系将棋AIをMacにインストールしてみた

# 背景
人間よりも強い将棋AIが登場して5年ほど経ち、近年ではプロ棋士がAIを使って研究したり、テレビ中継などでAIによる局面評価や次の推奨手が視聴者に示されるのもごく一般的なこととなってきた。そんな中、最近では深層学習を取り入れた、いわゆるDL系と呼ばれる将棋AIが台頭してきている。深層学習を取り入れた将棋AIの最初の成功例はDeepMind社が開発したAlphaZeroであろう。コンピュータ囲碁の世界で初めて人間のプロを打ち破ったAlphaGoの系統を汲む（汎化された）ボードゲームAIであり、囲碁に加えて将棋とチェスも指すことができる。AlphaZeroは2017年末、当時の世界コンピュータ将棋選手権優勝ソフトであったelmoに対し、100番勝負で実に90勝8敗2分という圧倒的成績を上げた<sup>[1, 2]</sup>。これ以降、コンピュータ将棋の分野においても、積極的に深層学習を取り入れようとする開発者が増えたようで、日本でもAobaZero<sup>[3]</sup>やdlshogi<sup>[4]</sup>などが開発されている。特にdlshogi（GCT）は2020年の第一回将棋AI電竜戦において優勝し、一躍DL系の台頭を印象付けた<sup>[5]</sup>。また、2021年の第二回将棋AI電竜戦エキシビジョンマッチにおいても、（従来のNNUE型評価関数を用いる最強AIとされる）水匠に対し、2勝1敗（いずれも先手番の勝利）と互角の戦いぶりを見せた<sup>[6]</sup>。

DL系将棋AIは従来型のものに比べ、局面を評価する精度が高い（大局観に優れている）反面、時間あたりに探索できる局面数が少なく、深く読むことが苦手とされているようだ<sup>[7, 8]</sup>。そのため、読みの深さよりも大局観で優劣が付きやすい序盤においてはDL系が強く、詰む詰まないを読み切ることが重要となる終盤においては従来型の方が強い、と今のところは考えられているようである。それはさておき、人間が将棋の研究にAIを活用するというシーンから見れば、序盤の大局観に優れているとされるDL系は大いに利用価値があると言えよう。既に藤井聡太三冠や渡辺明名人ら、トップ棋士がDL系将棋AIを研究に取り入れている<sup>[9, 10]</sup>というのも頷ける話である。

# ハードル
そんなわけで、ユーザーとしてはDL系の将棋AIもどんどん使っていきたいのであるが、問題はその動作環境を構築することだ。深層学習はその学習過程においてに膨大な計算資源が必要であることはよく知られているが、推論過程においてもやはりそれなりに計算資源が必要なようで、特に高速化しようとすればGPUの利用はほぼ避けて通れない。さらには、GPUプログラミングのデファクトスタンダードになっているCUDAを利用するため、GPUはnVIDIA製でなければならない、というところまでが大方決まっている現状だ。この時点でMacユーザーは涙目である。（Apple社はnVIDIA社と決別してしまったので）まあ、Windows PCであっても、コンピュータに詳しくない人にとっては、従来型のものと比べて導入が格段に厄介であることは間違いないようであるが<sup>[10-12]</sup>。

とはいえ、推論しかしないのであれば、コンパイルさえ通ればある程度CPUだけでも動かせるに違いないと方々調べ回った結果、従来型将棋AIでは（探索エンジンの）デファクトスタンダードになっている「やねうら王」<sup>[13]</sup>において、dlshogi互換の探索エンジン「ふかうら王」も既に実装されていた。推論にはMicrosoft社のonnxruntime<sup>[14]</sup>を利用しており、CPUのみの動作もサポートしている。これならば、Macでもなんとか動かせそうだなと思ったので、「ふかうら王」のコンパイルに挑戦してみることにした。

# 事前準備
まずはonnxruntimeをインストールしないといけないが、これはHomebrewのコアリポジトリに入っているため、既にHomebrewを導入している人であれば特段何かする必要はなく、

```
$ brew install onnxruntime
```
とコマンドを叩くだけで完了する。

# 「ふかうら王」のコンパイル
いよいよ本題の「ふかうら王」のコンパイルであるが、[「やねうら王」の本家GitHub](https://github.com/yaneurao/YaneuraOu)から最新のリリースをダウンロードしてきて、まずは`source/Makefile`をよく読んでみる。

```source/Makefile
（前省略）

ifneq (,$(findstring YANEURAOU_ENGINE_DEEP,$(YANEURAOU_EDITION)))
        ifeq ($(YANEURAOU_EDITION),YANEURAOU_ENGINE_DEEP)
        else
                # YANEURAOU_EDITIONがYANEURAOU_ENGINE_DEEPの後ろに文字が入っている名前なら、
                # とりあえず"YANEURAOU_ENGINE_DEEP"というシンボルを定義してやる。
                CPPFLAGS += -DYANEURAOU_ENGINE_DEEP

                ifeq ($(YANEURAOU_EDITION),YANEURAOU_ENGINE_DEEP_TENSOR_RT)
                        CPPFLAGS += -DTENSOR_RT
                        LDFLAGS += -lnvinfer -lnvparsers -lnvonnxparser -lcudnn -lcudart -lcublas
                        # CPPFLAGS += -I/usr/local/cuda/include
                        # LDFLAGS += -L/usr/local/cuda/lib64

                else ifeq ($(YANEURAOU_EDITION),YANEURAOU_ENGINE_DEEP_ORT_CPU)
                        CPPFLAGS += -DORT_CPU

                else ifeq ($(YANEURAOU_EDITION),YANEURAOU_ENGINE_DEEP_ORT_CUDA)
                        CPPFLAGS += -DORT_CUDA

                else ifeq ($(YANEURAOU_EDITION),YANEURAOU_ENGINE_DEEP_ORT_DML)
                        CPPFLAGS += -DORT_DML

                else ifeq ($(YANEURAOU_EDITION),YANEURAOU_ENGINE_DEEP_ORT_MKL)
                        CPPFLAGS += -DORT_MKL

                endif
        endif

（途中省略）

else ifeq ($(TARGET_CPU),AVX2)
        CPPFLAGS += -DUSE_AVX2 -DUSE_BMI2 -mbmi -mbmi2 -mavx2 -march=corei7-avx

（以降省略）
```

どうやら、`YANEURAOU_EDITION`という変数に `YANEURAOU_ENGINE_DEEP`が付く文字列を書いておけば良いらしい。CPU版としてコンパイルするならば`YANEURAOU_ENGINE_DEEP_ORT_CPU`で良さげ。`source/Makefile`を直接編集しても構わないが、`make`実行時に指定してやれば良い。
ただし、AVX2対応でコンパイルする場合（デフォルト）は、一箇所だけ`source/Makefile`を編集する必要がある。これはコンパイルオプションが`-march=corei7-avx`なっているためだが、このオプションではAVX2ではなくAVXまでしか有効にならない。そのため、`-march=core-avx2`と修正しておく<sup>[15]</sup>。（これに関しては、実は[以前の記事](https://qiita.com/Hikoyu/items/afc2cf372367f0d93331)を書いた時点で発見しており、「やねうら王」のFormulaのRubyスクリプトにもそのように実装してある）

続いて`source/config.h`の方も確認する。

```source/config.h
（前省略）

// --------------------
//  思考エンジンの種類
// --------------------


// やねうら王の思考エンジンとしてリリースする場合、以下から選択。(どれか一つは必ず選択しなければならない)
// ここに書かれていないエンジンもあるのでMakefileを見ること。
// オリジナルの思考エンジンをユーザーが作成する場合は、USER_ENGINE を defineして 他のエンジンのソースコードを参考に
//  engine/user-engine/ フォルダの中身を書くべし。

//#define YANEURAOU_ENGINE_DEEP            // ふかうら王
//#define YANEURAOU_ENGINE_NNUE            // やねうら王 通常探索部 NNUE評価関数
//#define YANEURAOU_ENGINE_KPPT            // やねうら王 通常探索部 KPPT評価関数
//#define YANEURAOU_ENGINE_KPP_KKPT        // やねうら王 通常探索部 KPP_KKPT評価関数
//#define YANEURAOU_ENGINE_MATERIAL        // やねうら王 通常探索部 駒得評価関数
//#define TANUKI_MATE_ENGINE               // tanuki- 詰め将棋solver  (2017/05/06～)
//#define YANEURAOU_MATE_ENGINE            // やねうら王 詰将棋solver (2020/12/29～)
//#define USER_ENGINE                      // ユーザーの思考エンジン

（途中省略）

// ---------------------
// ふかうら王(dlshogi互換エンジン)に関する設定。
// ---------------------


// ふかうら王で、ONNXRUNTIMEを用いて推論を行うときは、これをdefineすること。
// ※　GPUがなくても動作する。
//#define ONNXRUNTIME

// ふかうら王でTensorRTを使う時はこちら。
//#define TENSOR_RT

（以下省略）
```

`#define YANEURAOU_ENGINE_DEEP`の行と`#define ONNXRUNTIME`の行をアンコメントして有効にしておく。

それから、C++のソースコードファイルの`source/eval/deep/nn_onnx_runtime.cpp`にも1箇所だけ修正するところが見つかった。下記の部分。

```source/eval/deep/nn_onnx_runtime.cpp
（前省略）
                // Windows環境ではwstringでファイル名を渡す必要があるようだが？
                std::wstring onnx_filename = MultiByteToWideChar(model_filename);
                //std::string onnx_filename(filename);

                session.reset(new Ort::Session(env, onnx_filename.c_str(), session_options));

                return ResultCode::Ok;
（以下省略）
```

Windows環境向けのコードをコメントアウトし、通常のコードをアンコメントする。ついでに`onnx_filename(filename)`を`onnx_filename(model_filename)`と直しておく。

あとは、`make`時に`EXTRA_CPPFLAGS`と`EXTRA_LDFLAGS`にonnxruntime絡みのヘッダーファイルと共有ライブラリーのパス等を忘れずに渡してやれば良い。

のだが、もう一点あった。コンパイルのオプションに`-fexceptions`を付けておかないと、次のようなエラーがたくさん出てしまう。

```
/usr/local/include/onnxruntime/core/session/onnxruntime_cxx_api.h:182:7: error: cannot use 'throw' with exceptions disabled
      ORT_CXX_API_THROW("Allocation failure", ORT_FAIL);
      ^
```

上記のことに全て留意し（`source/config.h`と`source/eval/deep/nn_onnx_runtime.cpp`の該当箇所を修正した上で）次のように`make`を叩けばコンパイルが無事通る。（Homebrewを`/usr/local`以外にインストールしている人は適切なパスに直すこと）

```
$ cd YaneuraOu-6.50/
$ make -C source COMPILER=g++ EXTRA_CPPFLAGS="-I/usr/local/include/onnxruntime/core/session -I/usr/local/include/onnxruntime/core/providers/cpu -fexceptions" EXTRA_LDFLAGS="-L/usr/local/lib -lonnxruntime" YANEURAOU_EDITION=YANEURAOU_ENGINE_DEEP_ORT_CPU
```

なお、`COMPILER`は[以前の記事](https://qiita.com/Hikoyu/items/afc2cf372367f0d93331)で検討した通り、`g++`（実態は`clang++`だが）にしておくと、標準ライブラリの名前を変えたりしなくて済むようなのでラク。

また、`TARGET_CPU`（利用するSIMD拡張命令）の指定について上記では特に指定していないが、デフォルトで`AVX2`になっている。第10世代Core iシリーズのCPUを積んでいるIntel Mac（2020年上半期製）であれば上位の`AVX512`を指定できるはず。逆に、第3世代Core iシリーズ以前のCPUを積んでいるIntel Mac（2012年以前製）では下位の`SSE42`、未だにCore2 duoのInetl Mac（概ね2008年以降製）を使っているならば`SSE41`を指定しないと、コンパイルはできたとしても動作しないと思われる。

コンパイルが終了すると`source/YaneuraOu-by-gcc`ができているはずなので、簡単な動作確認をしておく。

```
$ echo "usi" | ./source/YaneuraOu-by-gcc
id name FukauraOu ORT-dlshogi-denryu2020 6.50 64AVX2
id author by Tadao Yamaoka , yaneurao
option name USI_Hash type spin default 16 min 1 max 33554432
option name USI_Ponder type check default false
option name Stochastic_Ponder type check default false
option name MultiPV type spin default 1 min 1 max 800
option name NetworkDelay type spin default 400 min 0 max 10000
option name NetworkDelay2 type spin default 1400 min 0 max 10000
option name MinimumThinkingTime type spin default 2000 min 1000 max 100000
option name SlowMover type spin default 100 min 1 max 1000
option name MaxMovesToDraw type spin default 0 min 0 max 100000
option name DepthLimit type spin default 0 min 0 max 2147483647
option name NodesLimit type spin default 0 min 0 max 9223372036854775807
option name EvalDir type string default eval
option name WriteDebugLog type check default false
option name GenerateAllLegalMoves type check default false
option name EnteringKingRule type combo default CSARule27 var NoEnteringKing var CSARule24 var CSARule24H var CSARule27 var CSARule27H var TryRule
option name USI_OwnBook type check default true
option name NarrowBook type check default false
option name BookMoves type spin default 16 min 0 max 10000
option name BookIgnoreRate type spin default 0 min 0 max 100
option name BookFile type combo default standard_book.db var no_book var standard_book.db var yaneura_book1.db var yaneura_book2.db var yaneura_book3.db var yaneura_book4.db var user_book1.db var user_book2.db var user_book3.db var book.bin
option name BookDir type string default book
option name BookEvalDiff type spin default 30 min 0 max 99999
option name BookEvalBlackLimit type spin default 0 min -99999 max 99999
option name BookEvalWhiteLimit type spin default -140 min -99999 max 99999
option name BookDepthLimit type spin default 16 min 0 max 99999
option name BookOnTheFly type check default false
option name ConsiderBookMoveCount type check default false
option name BookPvMoves type spin default 8 min 1 max 246
option name IgnoreBookPly type check default false
option name PV_Interval type spin default 500 min 0 max 2147483647
option name UCT_NodeLimit type spin default 10000000 min 100000 max 1000000000
option name DebugMessage type check default false
option name ReuseSubtree type check default true
option name Resign_Threshold type spin default 0 min 0 max 1000
option name DrawValueBlack type spin default 500 min 0 max 1000
option name DrawValueWhite type spin default 500 min 0 max 1000
option name Softmax_Temperature type spin default 1740 min 1 max 5000
option name UCT_Threads1 type spin default 4 min 0 max 256
option name UCT_Threads2 type spin default 0 min 0 max 256
option name UCT_Threads3 type spin default 0 min 0 max 256
option name UCT_Threads4 type spin default 0 min 0 max 256
option name UCT_Threads5 type spin default 0 min 0 max 256
option name UCT_Threads6 type spin default 0 min 0 max 256
option name UCT_Threads7 type spin default 0 min 0 max 256
option name UCT_Threads8 type spin default 0 min 0 max 256
option name DNN_Model1 type string default model.onnx
option name DNN_Model2 type string default 
option name DNN_Model3 type string default 
option name DNN_Model4 type string default 
option name DNN_Model5 type string default 
option name DNN_Model6 type string default 
option name DNN_Model7 type string default 
option name DNN_Model8 type string default 
option name DNN_Batch_Size1 type spin default 32 min 1 max 1024
option name DNN_Batch_Size2 type spin default 0 min 0 max 65536
option name DNN_Batch_Size3 type spin default 0 min 0 max 65536
option name DNN_Batch_Size4 type spin default 0 min 0 max 65536
option name DNN_Batch_Size5 type spin default 0 min 0 max 65536
option name DNN_Batch_Size6 type spin default 0 min 0 max 65536
option name DNN_Batch_Size7 type spin default 0 min 0 max 65536
option name DNN_Batch_Size8 type spin default 0 min 0 max 65536
option name LeafDfpnNodesLimit type spin default 40 min 0 max 10000
option name RootMateSearchNodesLimit type spin default 1000000 min 0 max 4294967295
usiok
```

「やねうら王」に比べてたくさん項目が表示された（オプションが多い）が、ちゃんと`usiok`が表示されたので大丈夫そう。
補足だが、おそらく`onnxruntime.dylib`は実行時に動的リンクしていると思われるので、環境変数`DYLD_FALLBACK_LIBRARY_PATH`に`/usr/local/lib`等のパスを書いておいた方が良いと思われる。（`~/.bash_profile`などに記述しておく）

# 学習済みモデルの入手と配置
ここまでで一応、Macでも「ふかうら王」をCPU動作版としてコンパイルできた。が、しかし、推論に必要な学習済みモデルがない！「ふかうら王」は探索エンジンのみなので、それだけでは将棋が指せないのだ。そこで、[「dlshogi」の本家GitHubの最新リリース](https://github.com/TadaoYamaoka/DeepLearningShogi/releases/tag/dr2_exhi)で公開されている学習済みモデルをダウンロードして使うことにする。
なお、この学習済みモデル（第二回電竜戦のエキシビジョンマッチで使われたもの）は誰でも無料でダウンロードして使って良いのだが、zipの解凍にパスワードが必要で、その取得にあたって<b>ライセンス文（日本語）をよく読み、同意する</b>必要がある。概ね個人利用の範疇であれば大きな問題はないはずだが、コンピュータ将棋大会に出場する場合はその利用方法に制限が課せられている。また、学習済みモデルのリバースエンジニアリングや改変、再配布も禁止されている。

ダウンロードした学習済みモデルを解凍すると、こんな感じになっている。

```
model-dr2_exhi
├── LICENSE.txt
├── model-dr2_exhi.onnx
└── model-dr2_exhi.onnx.ini
```

これらのファイルを「ふかうら王」の実行ファイルが置かれている`source/`下にある`eval/`に移動させれば良いのだが、モデルファイルの名前は`model.onnx`、初期設定ファイルの名前は`model.onnx.ini`に変更しておかなくてはならない。

```
$ mv model-dr2_exhi/LICENSE.txt YaneuraOu-6.50/source/eval/
$ mv model-dr2_exhi/model-dr2_exhi.onnx YaneuraOu-6.50/source/eval/model.onnx
$ mv model-dr2_exhi/model-dr2_exhi.onnx.ini YaneuraOu-6.50/source/eval/model.onnx.ini
```

# 試しに使ってみた
これで全ての準備が整ったので、[将棋所](http://shogidokoro.starfree.jp/mac/index.html)<sup>[15]</sup>等のGUIソフトにエンジンを登録して使うことができる。
なお、エンジン設定で`DNN_Batch_Size1`はチューニングした方が良いとのこと。（対局用なら128、検討用なら256が推奨値らしい）
他にも色々とオプションがあるが、正直私はあまりよくわかっていない。
![エンジン設定例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244335/c475ed96-8426-0633-b32f-09d065f8fa3e.png)

ともあれ、上記設定のもと私のMacBook Air (Early 2020, 第10世代Intel Core i7搭載)で実際に検討させてみたところ、ある程度の時間思考させると60NPS程度に落ち着いた。

# HomebrewのFormulaを作成
そんな感じでMacでの「ふかうら王」のコンパイル方法がわかったので、HomebrewのFormulaも作成した。お使いのMacにHomebrewをインストール後、次のコマンドを叩くだけで「ふかうら王」がインストールされるはず。

```
$ brew tap hikoyu/shogi # 初回のみ叩くコマンド
$ brew install fukauraou
$ brew test fukauraou
```

実行ファイルをGUIソフトに登録する方法については、[こちらの記事](https://qiita.com/mokemokechicken/items/2d2b188a7404d8c77d6a)が詳しいので参照されたい。

なお、dlshogiの学習済みモデルに関しては、製作者の方にも問い合わせてみたところ、インストールスクリプトには含めないでほしいという意向であったため、Formulaには含めていない。
使ってみたい方は各自でライセンスを確認の上、学習済みモデルを取得されたい。
その場合、`brew`で「ふかうら王」をインストール後、次のようにファイルを配置すると良い。（ただし、「ふかうら王」を再インストールしたり、いずれアップグレードすると配置した学習済みモデルも削除されてしまうので注意）

```
$ mkdir /usr/local/opt/fukauraou/eval
$ mv model-dr2_exhi/LICENSE.txt /usr/local/opt/fukauraou/eval
$ mv model-dr2_exhi/model-dr2_exhi.onnx /usr/local/opt/fukauraou/eval/model.onnx
$ mv model-dr2_exhi/model-dr2_exhi.onnx.ini /usr/local/opt/fukauraou/eval/model.onnx.ini
```

# まとめ
MacでCPUのみの動作ではDL系将棋AIの真の実力は到底引き出せないと思われるが、それでも従来型のものとは少し異なる評価値を見せてくれるところは面白いし、序盤の研究において課題局面を少し時間をかけてAIに考えさせてみるような使い方であればそれなりには使えるのではないかと思う。
今後、MacにおいてDL系将棋AIの高速化のためにGPU（主にCPU内臓GPU）を使えるようになっていくかどうかはよくわからないが、Apple社純正の機械学習フレームワークであるCore ML<sup>[17]</sup>を使った開発が進めば、一縷の望みはあるかもしれない。
この記事が敷居の高いDL系将棋AI導入の一助となれば幸いである。

# 2023/1/24追記
この記事を書いた当時、MacでGPUを使える将棋AIは存在せず、まとめのところでCore MLについて少し言及していた程度だったのだが、その後実際に「ふかうら王」にCore MLを対応させた開発<sup>[18]</sup>をされた方が現れ、個人的な希望的観測はいつの間にか現実のものとなっていた。
この開発に最大限の敬意を払い、HomebrewのFormulaを新たに作成したので公開する。
なお、この「ふかうら王 Core ML対応版」では、製作者の方が独自に学習させた3種類の学習モデルが公開されており、それらも自動的にインストールされるようにしている。

```
$ brew tap hikoyu/shogi # 初回のみ叩くコマンド
$ brew install fukauraou_coreml
$ brew test fukauraou_coreml
```

デフォルトでは`DlShogiResnet15x224SwishBatch.mlmodel`を学習モデルとして使うようになっているが、変更したい場合は次のようにすれば良い。（学習モデルのシンボリックリンクを貼り直した後、`brew test`もやり直しておく）

```
$ rm -r /usr/local/opt/fukauraou_coreml/eval/model.mlmodelc
$ ln -sf DlShogiResnet10SwishBatch.mlmodel /usr/local/opt/fukauraou_coreml/eval/model.mlmodel
または
$ ln -sf DlShogiResnet5x64SwishBatch.mlmodel /usr/local/opt/fukauraou_coreml/eval/model.mlmodel
$ brew test fukauraou_coreml
```

# 引用文献
1. https://deepmind.com/blog/article/alphazero-shedding-new-light-grand-games-chess-shogi-and-go
1. https://www.science.org/doi/full/10.1126/science.aar6404
1. http://www.yss-aya.com/aobazero/
1. https://github.com/TadaoYamaoka/DeepLearningShogi
1. https://denryu-sen.jp/dr1/result.html
1. http://blog.computer-shogi.org/denryu-sen_channel_opening-matches_and_conference/
1. https://logmi.jp/tech/articles/324157
1. https://yaneuraou.yaneu.com/2020/12/20/deep-learning-shogi-features/
1. https://times.abema.tv/articles/-/8669730
1. https://news.yahoo.co.jp/byline/matsumotohirofumi/20210816-00253492
1. https://news.livedoor.com/article/detail/20882210/
1. https://news.livedoor.com/article/detail/20882230/
1. https://github.com/yaneurao/YaneuraOu
1. https://onnxruntime.ai
1. https://software.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/compiler-options/compiler-option-details/code-generation-options/march.html
1. http://shogidokoro.starfree.jp/mac/index.html
1. https://developer.apple.com/jp/machine-learning/core-ml/
1. https://github.com/select766/FukauraOu-CoreML