MiniMax H3をApple Siliconで動かすためのMPS必須パッチと実測
今回やったこと
MiniMax H3をApple SiliconのM5 Pro・メモリ64GB環境で動かし、MPSで詰まる箇所をパッチで回避した。対象のワークフローでは、ComfyUI側のINT8経路が torch._int_mm を無条件に呼び出す。しかしMPSにはこの演算のカーネルがなく、素の状態ではサンプリングの1層目で NotImplementedError: aten::_int_mm ... MPS が発生して停止する。
問題はモデルの読み込み失敗ではなく、量子化された重みを処理する演算経路と、MPSが提供する演算の差にある。そこで registry.get_implementation をフックし、MPS上ではINT8重みをbf16へ展開して通常のLinearを実行する mps_int8_patch.py を用意した。site-packagesを直接変更しないため、環境を作り直したときもパッチの有無を追跡しやすい。
fp32エミュレーションを採用しなかった理由
最初は torch._int_mm をfp32行列積で厳密にエミュレートする案を試した。互換性を優先した考え方だが、MPSでのfp32 matmulはbf16より大幅に遅かった。実測ではfp32が28.4ms、bf16が7.0msで、約4.1倍の差になった。
この差を許容してINT8を名目上維持するより、ComfyUIの動作前提に合わせてbf16へ展開する方が実用的だった。パッチの目的はINT8という表記を守ることではなく、MPSで処理を完走させ、速度と結果を確認できる経路を作ることにある。
INT8重み
↓ MPS上の実装を判定
bf16へ展開
↓
通常のLinearで計算
実測した設定と結果
パッチv2、INT8 TE、gpu-only なしの条件で測定した。解像度・フレーム数・ステップ数を変えると、同じモデルでも処理時間が大きく変わる。確認できた値は次のとおりである。
| 解像度 | フレーム | steps | 実測時間 | 1 stepあたり |
|---|---|---|---|---|
| 608×352 | 39 | 4 | 84.5秒 | 7.4秒 |
| 640×640 | 39 | 20 | 8分27秒 | 22.0秒 |
| 640×640 | 124(5.17秒) | 20 | 33分09秒 | 71.3秒 |
音声を伴う出力では、話者の声質記述 (S1) says: <d>[Japanese] セリフ</d> の形式が必要だった。声質の記述を省くと叫び声に寄り、促音や長音を含む表現は意図と異なる音へ変化することがある。そのため、プロンプトは映像だけでなく音声の書式も実測結果に合わせて固定する必要がある。
Apple Siliconで避ける設定
text_encoder_device() は常にCPUを返す構成だった。--gpu-only を付けると確認時にはスワップが28GBまで増え、217秒から175秒へ短縮されたものの、メモリ圧迫を伴う結果になった。Apple Siliconでは、GPUへ寄せられる処理を増やすこと自体を目的にせず、CPUに残る処理とメモリの余裕を含めて測るべきである。
実装ステータスと適用範囲
パッチは作成済みで、M5 Pro・64GB環境での動作検証まで完了している。一方、MPS以外のバックエンドや別バージョンのComfyUIで同じ結果になるとは限らない。torch._int_mm の実装有無、重みの型、メモリ容量を確認してから適用する必要がある。エラーを見てモデルを諦める前に、失敗している演算と実行デバイスを切り分けるのが今回の要点である。
更新履歴
- 2026-09-08: 初稿。MPSのINT8経路、bf16回避策、実測値を整理。