ブラウザでKataGoを動かす

公開日:2026年10月24日 更新日:2026年10月25日

囲碁AIのKataGoを、サーバーを借りずに、遊ぶ人のブラウザの中だけで動かしたい。GPUで速く動かすのが近道のはずでした。先に動かなくなったのは、そのGPUのほうでした。

なぜ、ブラウザの中で計算するのか

AIの計算をサーバーでやると、遊ぶ人が増えるほど費用がかかります。ブラウザの中で動かせば、サーバーが配るのは静的なファイルだけです。無料の枠で長く運用できて、広告や商用展開の余地も残せます。「おるふぇ碁」を、その形で作ることにしました。

まず測る

最初に、Kayaという既存アプリが配っている、約75MBのモデル(b28c512、ONNX形式)を、ONNX Runtimeで、WebAssembly(CPU、1スレッド)とWebGPUに分けて動かして、推論1回の時間を測りました。環境は、Windows、Chrome、内蔵GPU、メモリ16GB、8コアです。

WASM(CPU・1スレッド)約1.6秒WebGPU約3.5秒推論1回あたり。約75MBのモデル、Windows、Chrome、内蔵GPU

私の環境では、GPUのほうが2倍以上遅い結果でした。別の環境では、違う結果になるかもしれません。ただ、この時点で決めていたのは「思考時間にこだわらず、棋力と確実さを優先する」ことです。速さの比較だけでは、決める材料が足りませんでした。

コードは借りない

既存のアプリのコードは流用しませんでした。ライセンスの自由度を残しておけば、広告収益や商用展開を、あとからでも選べるからです。モデルは、KataGoのg170e系(公開のモデル配布ページから入手)を使います。ブラウザの中で探索までできるエンジンとしては、katago-webgpuをビルドして再利用しました。

モデルの名前について

KataGoのモデルは、名前の「b」がブロック数、「c」がチャンネル数を表していて、数字が大きいほど大きなモデルです。この記事には、小さいほうから順に、b6c96、b10c128、b18c384が出てきます。最初に測った約75MBのモデルは、b28c512という、さらに大型のものでした。

WebGPUで落ちる

ここからが長くなりました。b18c384という大きめのモデルで、探索の途中にWorkerが落ちます。ログには、WebGPUのディスパッチ上限(65535)を超えるという警告が出て、そのあとに「nonfinite for policy sum」、続いてAborted()と出ます。

上限を直すパッチを当てると、警告は消えました。でも、落ちるのは残りました。小型のb10c128にしても、今度は別のassert(totalNewUnnormWeight > 0.0)で落ちて、Firefoxで動かしても同じでした。試したことをまとめると、次のとおりです。

試したこと結果
ディスパッチ上限のパッチ警告は消えるが、落ちるのは残る。外しても同じエラー
サブグループを無効にして再ビルドb10c128は同じassertで落ちる
Firefoxで実行同じassertで落ちる
tanhのクランプb6c96の「白がパスを多用する」は直るが、辺に打つなどひどい手を選ぶ
CPUバックエンド(?cpu、NHWCパッチ後)b6c96とb10c128の両方が、数手打った範囲で問題なく動く

ひとつ引っかかっていたのは、別の実装のWeb KaTrainが、同じPCのWebGPUで問題なく動いていたことです。「このPCのGPUの問題」と片づけるわけにはいきません。実装側の原因も疑いました。それでも、GPUの経路は最後まで解決しませんでした。

一局打たせて、CPUに決める

CPUで動くことは分かりました。ただ、数手動くことと、碁として使えることは別です。b10c128(CPU)を、Web KaTrainのデフォルトと対局させました。結果は、黒番で圧勝でした。大石の生き死にも、b10c128のほうが正しく判断できていました。

これで方針が決まりました。まずCPUで作る。形ができて余裕があれば、GPUを考える。ブラウザ用のエンジンは、katago-webgpuに含まれるkataevalです。KataGoの探索を、WebAssemblyにまとめた部分です。これに、日本ルールで動くパッチを当てて再ビルドしました。モデルは、b6c96をやめて、b10c128にしました。

遅さの原因を突き止めエンジンを直した

探索のスレッドは、使えるコア数に合わせて(最大8本まで)使うようにしました。ところが、実際に測ると、スレッドを増やしても思ったほど速くなりませんでした。8コアのPCで、中盤の局面を300回探索させたところ、1本で毎秒13回、8本で毎秒20回です。8倍にはならず、1.5倍ほどでした。19路盤は毎秒5回で、100回読むのに約20秒かかりました。

エンジンの作り方を見直すと、理由が2つ見つかりました。ネットの評価をするスレッドが1本に固定されていたことと、wasmのSIMD(まとめて計算する機能)を使っていなかったことです。評価スレッドは、最初に作ったWebGPU版の都合で1本にしていた設定を、CPU版にも引き継いでいました。そこで、CPUのときだけ、評価スレッドをコア数(最大8)にしました。あわせて、行列計算をSIMDでビルドし直しました。

結果は、9路盤の300回探索で、1本が毎秒49回、8本が毎秒144回でした。8本で、以前の約7倍です。盤の大きさ別に8本で測ると、9路は毎秒91回、13路は毎秒76回、19路は毎秒54回でした。19路盤でも、100回読むのに約2秒です。実際に対局させると、9路盤の標準設定で、AIの1手は1〜7秒、19路盤で、10〜20秒ほどでした。

ここまでは、「読みの深さ」の回数を見直す必要があるかもしれないと考えていました。遅いのは、設定の回数が多すぎるからだと思ったからです。でも、原因はエンジンの作りのほうにあり、そこを直したことで、回数は見直さずに済みました。評価に必要な回数だけ、盤の大きさによらず100回にそろえました。

速くなったら、端末が熱くなった

エンジンを直して、すべてのコアを使うようになると、今度は端末が熱くなりました。AI同士の対局を続けて眺めると、CPUがずっと全力で動くからです。そこで、AI同士のときだけ、読むスレッド数を半分にして、前の手を考えた時間の3割ほど、手のあいだに休みを入れるようにしました。棋譜の「全体を再解析」も、長い棋譜だと何分も全力で読み続けるので、あとから同じ仕組みを入れました。1局面を読み終えるごとに、読んだ時間の3割ほど休みます。どちらの値も、AIの負荷設定のページで変えられます。

おるふぇ碁を遊ぶ

← Orfevre Lab トップへ戻る