TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

dropless MoE(Mixture of Experts)を試す その6(推論速度比較)

前回、C++で実装したTensorRTプラグインを使ったMoEの推論処理の推論速度を比較する。

比較対象は、

  • Dense MoE
  • Sparse MoE (自前実装CUDAカーネル)
  • Sparse MoE (CUTLASS Grouped GEMM)

の3パターンとする。

比較条件

  • SwinTransformerのStage 0/1をMoE化
    • Stage 0の解像度 8x8、State 1の解像度 4x4
    • Stage 0のhidden_features 256、State 1のhidden_features 512
  • Expert数4
  • top_k=2
  • バッチサイズ128
  • FP16
  • RTX 4090で測定

前回実装した推論コマンドを呼び出す、以下のベンチマークスクリプトを使用する。

train_cifar10/scripts/benchmark_moe_trt.py at feature/moe · TadaoYamaoka/train_cifar10 · GitHub

測定結果

CUDA Graph 有効

batch=128 warmup=5 iters=10 cuda_graph=True

target avg_latency_ms throughput_images_per_s
Dense MoE 0.582870 219603.000
Sparse Plugin MoE 1.790130 71503.000
Sparse Plugin MoE (CUTLASS) 0.947411 135105.000

CUDA Graph 無効

batch=128 warmup=5 iters=10 cuda_graph=False

target avg_latency_ms throughput_images_per_s
Dense MoE 1.028980 124395.000
Sparse Plugin MoE 2.067490 61910.700
Sparse Plugin MoE (CUTLASS) 1.332480 96061.200

考察

Dense MoEが一番早く、Sparse MoEでは、CUTLASSを使った方が速いという結果になった。

以下に結果を考察する。

1. 概要

主因は、今回の条件では MoE を sparse にするメリットが小さく、sparse 実行のオーバーヘッドが相対的に大きいためであると考えられる。 結果を比較すると以下の通りである。

手法 Latency 備考
Dense MoE 0.583 ms 最速
Sparse Plugin MoE 1.790 ms Dense比 約3.07倍低速
Sparse Plugin MoE (CUTLASS) 0.947 ms Dense比 約1.63倍低速

CUTLASS 版は非 CUTLASS 版より約 1.89 倍高速であり、CUTLASS grouped GEMM 自体は有効に機能している。しかし、依然として Dense に及ばないという点が極めて重要である。

2. 理論メリットの限定性

今回の MoE は E=4, top_k=2 であるため、sparse 化による理論上の計算量削減幅が小さい。

  • Dense MoE: 全 expert (E=4) を計算。
  • Sparse MoE: top-k expert (K=2) のみを計算。

expert MLP の計算量のみに着目すれば、Sparse は Dense の約半分となる。しかし、モデル全体には Patch embedding、LayerNorm、Window attention、TensorRT の入出力処理などが含まれる。 モデル全体のレイテンシに対する MoE expert 計算の支配率が 100% でない限り、全体の短縮幅は 2 倍未満に留まる。この時点で Sparse が勝利するには、routing や packing などの追加コストを極小化する必要がある。

3. Dense MoE における TensorRT の最適化

Dense 版は全 expert を規則的な tensor 演算(torch.einsum 等)として実行する。これは算術量こそ増えるものの、GPU および TensorRT にとっては極めて扱いやすい。

  • contiguous な tensor 構造
  • 静的かつ規則的な shape
  • gather / scatter の抑制
  • TensorRT の標準オペレータとしての最適化
  • cuBLAS / Tensor Core tactic への適合

特に E=4 のような少数 expert 設定では、「計算の無駄」よりも「規則的な dense matmul による高速化」のメリットが上回る。

4. Sparse Plugin におけるオーバーヘッド

Sparse MoE は計算量を削減する代償として、不規則なメモリアクセスや多数のカーネル実行を必要とする。具体的には、routing、prefix sum、token assignment の packing、scatter-add、atomic add といった処理が Dense 版にはほぼ不要な追加コストとして発生する。 E=4, K=2 という設定では、削減できる計算量よりも、これら不規則処理のオーバーヘッドが支配的になりやすい。

5. Plugin 境界による最適化の制限

plugin モードでは、MoE 全体が trt.plugins::CustomMoE という単一の custom node となり、TensorRT から見て内部が不透明(opaque)になる。

  • TensorRT による layer fusion が適用されない。
  • 内部のカーネルに対して TensorRT の tactic selection が効かない。
  • カーネル起動やメモリアクセスの効率が、独自実装の品質に完全に依存する。

対して Dense 版は標準 ONNX オペレータのグラフであるため、TensorRT が広範囲にわたってグラフ最適化を適用できる。この差は極めて大きい。

6. Batch Size 128 における実行効率

今回のバッチサイズ 128 では、特に stage 0 等でトークン数が多くなる(例:128 * 64 = 8192 tokens)。 Dense MoE はこのまとまったトークン数を高 occupancy・高 arithmetic intensity で処理できる。一方、Sparse MoE では expert ごとにトークンが分割されるため、個々の GEMM サイズが小さくなり、metadata のセットアップやスケジューリングのオーバーヘッドによる効率低下の影響を受けやすくなる。

7. CUDA Graph の限界

cuda_graph=True の設定により CPU 側の起動オーバーヘッドは削減されているが、GPU 上での routing 計算、packing copy、atomicAdd といったデバイス側のコストは消失しない。今回の結果は、CPU launch ではなくデバイス側のオーバーヘッドが支配的であることを示唆している。

8. 結論:Dense が勝る理由と今後の展望

GPU 最適化において「無駄な計算をしても規則的な演算の方が速い」という典型的な事例である。

優先度の高い仮説

  1. E=4, K=2 では計算削減の恩恵がオーバーヘッドを相殺できない。
  2. Dense ONNX は TensorRT の標準最適化を最大限に享受している。
  3. Sparse plugin 内の routing や atomic 処理が依然として重い。

改善に向けたアプローチ:

今後 Sparse plugin で Dense を上回るには、少数 expert への特化(kernel fusion の徹底)、atomicAdd の排除、あるいはトークン数や設定に応じて Dense と Sparse を動的に切り替えるハイブリッド戦略の検討が有効である。

まとめ

TensorRTプラグインで実装したMoEの推論速度を比較した。
結果、Dense MoEが一番速いという結果になった。Sparse MoEではCUTLASSを使用することで約1.89 倍速くなることが分かった。

今回測定したSwinTransformerはモデルサイズが小さく、CIFAR-10の画像解像度も小さいため、Sparse化のオーバーヘッドが大きくかえって遅くなるという結果になった。

また、実装したCUDAカーネルはkernel fusionなどの最適化を行っていないため、改良の余地がある。
LLMの推論ライブラリとして人気のvLLMは、デバイスごとの手書きCUDAカーネルで最適化を行っている。
そこで使われている手法を参考すれば、まだまだ最適化できると思っている。

SwinTransformerでの実験はこれまでにして、次は、dlshogiのモデルにMoEを組み込むことを試したい。