機械学習周りのプログラミング中心。 イベント情報
ポケモンバトルAI本電子書籍通販中

ふかうら王でMacのCoreMLを使う(成功)

ふかうら王(やねうら王のMCTS+DNNバージョン、つまりDL系将棋AI)でMacGPU, Neural Engineを使うことでMac上で高速動作できるようにしました。

2022年6月13日にやねうら王本家にマージされました。

動かし方

YaneuraOu-v7.6.3-macos.tar.xz を以下のページからダウンロードし、解凍。エンジン本体はFukauraOu-CoreMLディレクトリ内にあります。CPUアーキテクチャにより、M1とAVX2 (Intel系)を使い分けてください。

github.com

評価関数ファイルですが、通常のONNX形式のものが使えず、Apple独自のMLModel形式が必要です。以下のページにサンプルモデルファイルと、将棋所の設定について記載しています。

Release Core ML版サンプルビルド20220613 · select766/FukauraOu-CoreML · GitHub

M1 MacBook Air (2020年)でモデルDlShogiResnet15x224SwishBatch.mlmodel*1が1300NPS程度で動作します。計算にはNeural Engineが使われています。

技術解説

ふかうら王でCore MLを利用するためのプルリクエストが以下になります。

MacのCore MLインターフェース by select766 · Pull Request #249 · yaneurao/YaneuraOu · GitHub

C++製のアプリからObjective-Cを経由してCore MLを呼び出す手段については過去の記事で解説しました。

select766.hatenablog.com

この記事では、ふかうら王の中にCore MLインターフェースを実装する手段を説明します。

source/eval/deep/nn_coreml.h

class NNCoreML : public NN
{
public:
    virtual ~NNCoreML();

    // モデルファイルの読み込み。
    virtual Tools::Result load(const std::string& model_path , int gpu_id , int batch_size);

    // NNによる推論
    virtual void forward(const int batch_size, PType* p1, PType* p2, NN_Input1* x1, NN_Input2* x2, NN_Output_Policy* y1, NN_Output_Value* y2);

    // 使用可能なデバイス数を取得する。
    static int get_device_count();

private:
    int fixed_batch_size; // バッチサイズは、常にload()に与えられたもので動作する。動的にバッチサイズを変更すると実行計画の再生成が起こり、極めて遅くなる。
    void* model; // Objective-Cの型を見せないため
    DType* input_buf;
};

ふかうら王の評価関数の実装は、NNインターフェースとして切り出されています。すでにONNXRuntimeやTensorRTインターフェースがこれに準拠しています。 この仕様に沿った形でCore MLインターフェースを追加すればよいということになります。ここではNNCoreMLクラスとして実装しました。

実装本体はsource/eval/deep/nn_coreml.mmにあります。これはObjective-C++ソースです。以前の記事で書いたとおりのCore ML呼出し手順を記述すればOKです。

注意点として、 NNCoreML::forward(const int batch_size, PType* p1, PType* p2, NN_Input1* x1, NN_Input2* x2, NN_Output_Policy* y1, NN_Output_Value* y2) に渡されるバッチサイズが呼び出しごとに変動することが挙げられます。このバッチサイズをCore MLに渡すと、前回のバッチサイズと異なる場合に実行計画の再生成が起こり、極めて動作が遅くなります。バッチサイズが1→8→1→8と変化すると、毎回再生成されるようです。そのため、NNCoreML::load(const std::string& model_filename , int gpu_id , int batch_size)に渡されたバッチサイズ(forwardで渡される可能性がある最大バッチサイズ)を常に用いるようにします。forwardで小さいバッチサイズが渡されたとしてもパディングして、Core MLでは常に最大バッチサイズで推論させます。バッチサイズ8程度で速度が飽和するため、バッチサイズが8に満たない入力をパディングしたとしても大きく損をすることはありません。

最後にビルドスクリプトの変更ですが、Objective-C++をビルドする機能が必要です。Mac環境のclangではObjective-C++が問題なくビルドできるのでわずかな修正で済みます。

リンカオプションとして、Objective-Cで用いる標準ライブラリを指定するため-frameworkの指定を追加します。

     else ifeq ($(YANEURAOU_EDITION),YANEURAOU_ENGINE_DEEP_COREML)
            CPPFLAGS += -DCOREML
            LDFLAGS += -framework Foundation -framework CoreML
            OBJC_SOURCES += eval/deep/nn_coreml.mm

Objective-C++のソースのビルドは、$(CPPFLAGS)のほかに-fobjc-arcObjective-Cガベージコレクションのためのオプション)を付与します。出来上がる*.oC++をビルドしたものと同等に扱えます。

$(OBJDIR)/%.o: %.mm
    @[ -d $(dir $@) ] || mkdir -p $(dir $@)
    $(COMPILER) $(CPPFLAGS) -fobjc-arc $(INCLUDE) -o $@ -c $<

以上の修正により、Mac上でもハードウェアの性能を最大限生かしてDL系将棋AIを動作させることが可能となりました。NVIDIAGPUが搭載されたWindows環境には及びませんが、手持ちのMac環境を活用してみたいという方はお試しください。

2022-07-02追記: 利用者から連絡がありました。M1 UltraではNeural Engineのコア数がM1よりも多いはずですが、速度は変わらないそうです。Neural Engineの挙動に関してチューニングができるAPIが存在しないため、現時点では工夫する余地がありません。

*1:モデル構造は第2回世界将棋AI電竜戦エキシビションのdlshogiとほぼ同じですが、精度差については不明です