SSDからエキスパートの重みをストリーミングして、48GBしかないMacBookで397Bパラメータのモデルを動かす。Flash-MoEというプロジェクトを見つけたとき、正直に言えば、胸が躍った。

Qwen3.5-35B-A3Bの事前パック済みモデルがHugging Faceに上がっていて、しかもiOSアプリまであるらしい。35Bパラメータのうちアクティブはたった3B。MoE(Mixture of Experts)の構造を使って、推論に必要なエキスパートだけをNVMeから引っ張ってくる。Appleの「LLM in a Flash」論文の思想を、C言語とMetal shaderだけで実装した、いかにも個人開発者らしいゴリゴリのプロジェクトだった。

読んでいて楽しかった。技術的には、かなり筋がいい。

でも、「これを自分の環境に入れるか?」と聞かれたら、答えはノーだ。

iPhoneで動く、の現実

「iPhoneで動く」という一文に惹かれて調べ始めた。実際、GitHubのfeatureブランチにはiOSアプリのコードがある。が、冷静に見ると前提条件がきつい。

モデルデータはTiered量子化でも約18GB。ストレージには載る。でもRAMは8GB。Flash-MoEの最小要件は24GB。3倍足りない。

macOSならページキャッシュがエキスパートの出し入れをうまく管理してくれるけど、iOSはメモリが足りなくなった瞬間にアプリを殺す。そもそもの設計思想が違う。SSD帯域もM3 Maxの17.5GB/sが前提の数字であって、iPhoneのNANDでは出ない。

つまり、「iPhoneで動く」は技術デモとしては面白いが、実用ではない。少なくとも今のハードウェアでは。

デカいモデルが動いて、で?

M5 Maxの128GBなら話は変わる。35B-A3Bどころか122B-A10Bも射程圏内だ。SSDストリーミングなら、メモリに全部載らなくても動く。

ここで手が止まった。デカいモデルが手元で動く。それは嬉しい。でも何が嬉しいんだろう、具体的には。

よく言われるのが、コスト、レイテンシ、プライバシー、カスタマイズの自由度。どれも嘘じゃないんだけど、一個ずつ見ると微妙にピンとこない。

コストの話をすると、そもそもローカルマシンの初期投資が60万円を超える。APIの従量課金と比べたときに何ヶ月で回収できるかというと、普通の使い方なら相当かかる。レイテンシも、クラウドAPIがどんどん速くなっていて、ネットワーク遅延が致命的になるシナリオはかなり限定的だ(新幹線で作業したいとか)。プライバシーは正当な理由だけど、大多数にとっては理論上の懸念にとどまる。

カスタマイズだけは少し引っかかった。ファインチューニングやLoRAでモデル自体に自分のスタイルを焼き込めるのは面白い。面白いんだけど——「やれる」と「やる価値がある」の間にはけっこう深い溝があるのね。プロンプトで済むことを、わざわざモデルごと焼き直す必要があるのかと問われると、うーん、となる。

結局、どの恩恵も「あれば嬉しい」ではあるけど、35Bのモデルを回すためにマシンリソースを専有させるほどの理由にはならなかった。

ギークのロマンだと認めてしまう

Flash-MoEの技術は本当に面白い。SSDストリーミングでページキャッシュに任せる設計思想。Tiered量子化でホットなエキスパートだけ4bitを維持し、コールドを2bitに落とすアイデア。FMA最適化されたMetalカーネル。

何より好きなのは、READMEに載っている「試したけど駄目だった実験」の記録だ。成功した手法よりも、失敗した手法のほうが学びが多い。全部は載せきれないけど、特に印象的だったものを抜粋する。

**採用された最適化(効果があったもの)**

| 手法 | 効果 | ひとこと |

| FMA dequantカーネル | +12% tok/s | GPU演算の内部ループを`fma`命令に書き換え。地味だけど一番効いた |

| OSページキャッシュに委ねる | +38% | 自作のMetal LRUキャッシュを捨てた瞬間に速くなった。皮肉 |

| GPU combine+normをCMD3に統合 | パイプライン改善 | CPUラウンドトリップを1回消すだけで全体が滑らかに |

| Accelerate BLASでDelta-Net演算 | +64%(attention部) | Apple純正の行列演算ライブラリが結局最速だった |

| C言語BPEトークナイザー | 起動20倍速 | Pythonの3500ms → Cの180ms。起動時間の話だけど体感が違う |

| Tiered Expert量子化(hot=4bit, cold=2bit) | ディスク-34% | よく使うエキスパートだけ高精度を維持。品質劣化なし |

**却下された最適化(やったけど駄目だったもの)**

| 手法 | 結果 | なぜ駄目だったか |

| LZ4圧縮 | -13% | 解凍のオーバーヘッドがウォームキャッシュの恩恵を上回った |

| F_RDADVISEプリフェッチ | ±0% | 統合メモリ上でSSD DMAがGPUを73%遅くする。プリフェッチが逆効果 |

| 時系列エキスパート予測 | -18% | 的中率25%。外れたぶんだけSSD帯域を無駄食い |

| MLPルーティング予測器 | 的中率31% | 時系列ベースラインより微増だが、オーバーヘッドで帳消し |

| GPU LUTデクオントカーネル | -2% | 間接レジスタアクセスがシリアライズして遅くなった |

| GPU privateバッファ圧縮 | -20% | Blitコストが4×7MBかかり、matvecの節約を超えた |

| スピンポールGPU待機 | -23% | CPUの発熱がGPUのサーマルスロットリングを誘発 |

| dispatch_io | -70% | dispatch_dataの管理オーバーヘッドが壊滅的 |

| mmap(エキスパートファイル) | -5倍 | コールドデータのページフォルトが1枚ずつ発生して激遅 |

| 投機的早期ルーティング | -38% | キャッシュ汚染+オーバーヘッドで大幅悪化 |

| MTP投機的デコーディング | ±0 | MoEはトークンごとにI/Oが発生するので、denseモデルのような投機的並列が効かない |

この「やったけど駄目だった」リストが、このプロジェクトで一番価値のある成果物だと個人的には思っている。こういう記録を残すプロジェクトは信用できる。

でも、それは「技術として面白い」のであって、「自分の作業環境に入れたら生産性が上がる」とはまた別の話なのだと思う。

これは、ギークのロマンだ。ロマンを感じること自体は悪くない。ただ、ロマンだと認めてしまったほうが、次に何をすべきかがはっきりする。

本当に欲しいのは「同時に立ち上がっていること」

冷静に考えると、自分がローカルの計算資源に求めているのは「デカいモデルが動く」ことじゃなかった。「必要なものが全部、同時に、切り替えなしに動いていること」だった。

Claude Code(これはAPI経由だからローカルリソースは食わない)。会話の整理やプロンプトの下書きをやってくれる軽量LLM。音楽生成のACE-Step。この3つが並行して立ち上がっていて、タスクの切り替えなしに使える。

それが、128GBの統合メモリの本当の使い道だと思っている。

軽量LLMはQwen3.5の9Bクラスで十分。MLXで常駐させてもメモリは5〜6GB。ACE-StepはGPUとANEをそれなりに食うけど、128GBあれば余裕で同居できる。この組み合わせが、壊れずに、黙って、常に動いている状態。

35Bのモデルをわざわざ立ち上げて、SSD帯域を専有されて、他のツールが圧迫されるのは、むしろ邪魔だ。本末転倒、というやつだと思う。

技術との正しい距離感

Flash-MoEに価値がないわけじゃない。メモリに収まらないモデルを動かすためのフォールバックとしては心強い。122Bクラスをどうしてもローカルで試したいとき、この選択肢があること自体に意味がある。日常的に使うものではないけど、引き出しの奥にあると安心する工具のようなもの。

それと、あの失敗実験のリスト(上に抜粋を載せた)は、そのまま技術記事のネタとして一級品だ。最適化の成功談は世の中に溢れているけど、「やったけど駄目だった」を定量的に記録している事例はそう多くない。

技術は、自分の環境に入れることだけが付き合い方じゃない。読んで、考えて、自分の設計判断に取り込む。そういう距離感もある。Flash-MoEとの正しい距離は、たぶんそこだ。

いまの自分に必要なのは、397Bのモデルを動かすことじゃなくて、9Bのモデルと音楽生成ツールが静かに常駐している環境。

ロマンは嫌いじゃない。でも、作業環境はロマンではなく実利で組みたい。


Flash-MoEを自分の目で見てみたい人へ

この記事で取り上げたFlash-MoEは、Apple Silicon上でQwen3.5 MoEモデルをSSDストリーミングで動かすオープンソースの推論エンジン。Pythonもフレームワークも使わず、C/Objective-CとMetal shaderだけで書かれている。

M3 Max 48GBのMacBookで397Bモデルが4.36 tok/sで動く。自分の環境に入れるかどうかは別として、READMEの「やったけど駄目だった実験リスト」だけでも一読の価値がある。最適化に興味がある人なら、たぶん1時間は溶ける。