ブラウザで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コアです。
私の環境では、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の負荷設定のページで変えられます。