人の書いたソースを調べる際、動かせるようにしてからデバッガでステップ実行すると理解しやすい。
Leela Chess Zeroのソースをたまに参照していたが、詳細に実装を調べるために、デバッガで動かせるようにした。
公式のビルドの説明の通りだが、手順をメモしておく。
GitHubからソースをクローンする
git clone https://github.com/LeelaChessZero/lc0.git
ビルドシステムのmesonのバグなのか、Cドライブに配置しないとビルドでエラーが発生した。
Intel MKLをインストールする
Intelのサイトから、MKLをダウンロードしてインストールする。
インストーラは2種類あるが、フルインストール版はFortranもインストールされるので、不要であればカスタムインストール版を選んだ方がよい。
なお、OpenBLASでもビルドできるようになっているが、ビルドはできたがlibopenblas.dllでエラーが発生して実行できなかった。
Intel MKLでは問題が起きないので、Intel MKL版でビルドした。
mesonをインストールする
pipでmesonをインストールする。
pip install meson
build-cl.cmdを編集する
[編集前]
meson.py build --backend vs2017 --buildtype release ^ -Dmkl_include="C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\include" ^ -Dmkl_libdirs="C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\lib\intel64" ^ -Dopencl_libdirs="C:\Program Files (x86)\AMD APP SDK\3.0\lib\x86_64" ^ -Dopencl_include="C:\Program Files (x86)\AMD APP SDK\3.0\include" ^ -Ddefault_library=static
[編集後]
meson build --backend vs2017 --buildtype release ^ -Dmkl_include="C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\include" ^ -Dmkl_libdirs="C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\lib\intel64" ^ -Ddefault_library=static
meson.pyに拡張子があると実行できないので削除する。
openclは使用しないのでオプションから削除する。
MSVS projectを作成する
コマンドプロンプトを起動して、
"C:\Program Files (x86)\IntelSWTools\compilers_and_libraries\windows\mkl\bin\mklvars.bat" intel64
を実行する。
git cloneしたディレクトリに移動して、
build-cl.cmd
を実行する。
プロジェクトファイルが作成された時点でビルドを行うかプロンプトが表示されるので、終了する。
そのままビルドしてもよいが、Visual Studioで開いて実行する際に再度ビルドすることになる。
プロジェクトを開いてビルドする
buildディレクトリに、lc0.slnが作成されているので、Visual Studio 2017で開いてビルドする。
ネットワークを取得する
Leela Chess Zeroのプロジェクトのサイトからネットワークをダウンロードして、buildディレクトリに配置する。
ネットワークは実行速度が速い方がよいため、Blocks 10、Filters 128のものを選択する。
デバッガで実行する
Visual Studio 2017で、lc0のプロジェクトをスタートアッププロジェクトに設定して、デバッグ実行する。
コンソールに以下のように入力する。
isready position startpos go
探索が開始するので、デバッガで中断する。
デバッグ->ウィンドウ->並列スタックを表示して、実行中のスレッドのスタックを調べる。
停止するタイミングによって、スタックの状態が異なるが、そこからステップ実行することで処理の流れは把握できる。
Leela Chess Zeroの探索のメイン処理は、search.ccのSearchWorker::ExecuteOneIteration()に記述されている。
void SearchWorker::ExecuteOneIteration() { // 1. Initialize internal structures. InitializeIteration(search_->network_->NewComputation()); // 2. Gather minibatch. GatherMinibatch(); // 3. Prefetch into cache. MaybePrefetchIntoCache(); // 4. Run NN computation. RunNNComputation(); // 5. Retrieve NN computations (and terminal values) into nodes. FetchMinibatchResults(); // 6. Propagate the new nodes' information to all their parents in the tree. DoBackupUpdate(); // 7. Update the Search's status and progress information. UpdateCounters(); }
UCTの探索の処理は、名前が分かりにくいがGatherMinibatch()で行っている。
調べた内容のメモ
- 探索とネットワークの推論は、dlshogiの実装とほぼ同じで直列化されていた。
- 不要になったノードのガベージコレクションを専用スレッドで行っている。
- ucb1のベストノードをキャッシュしている(バーチャルロスの数と比較して再計算が不要な場合再利用している)
- ノードのハッシュは使用していない(木構造をノードのリンクで保持している)
- 探索はマルチスレッド(オプションのThreadsで変更できる)
- ノードを更新する際は、そのノードのみをロックする
- NNの結果は、履歴局面のハッシュをキーにしてキャッシュしている。