メモリの壁の「もう半分」を壊す——35B MoEをSSDからストリーミングするEdge0と学習済みルーティング予測
はじめに:MoE推論の「メモリの壁」はどこにあるのか
「メモリの壁(memory wall)」という言葉は、1995年にWulfとMcKeeが「プロセッサの進歩に対してメモリの速度と帯域が追いつかない」という構造的な乖離を名付けたものだ。それから30年、AI推論の現場では同じ壁が別の姿で立ちはだかっている。原因は、計算そのものではなくデータの運搬にある。
大規模言語モデルのデコード(1トークンずつ生成する処理)は、極端に低い演算強度を持つ。重みを1バイト読み出す間に、数回の浮動小数点演算しかしない。つまりボトルネックは計算量(FLOPs)ではなく、メモリから運ばなければならないバイト数——すなわち容量と帯域である。
推論時にメモリへ載るデータは、性質の異なる2種類に分かれる。
- 状態(KVキャッシュ):生成のたびに伸びていく動的な部分。MLA(Multi-head Latent Attention)、スパースアテンション、線形状態モデル(Mamba系)といった手法で圧縮が進み、「攻められた半分」になりつつある。
- 重み:学習が終わった瞬間に固定される静的な部分。数十GB単位で、これまでは「データセンターに置く」以外の答えがなかった「攻められていない半分」。
本記事が扱うのは後者である。鍵になるのは「重みを小さくする」ことではなく、「重みをどこに置くか」を変えるという発想の転換だ。
ここでMixture-of-Experts(MoE)の構造を確認しておこう。MoEは、ルーターと呼ばれる小さなゲートがトークンごとに少数のエキスパート(専門家サブネットワーク)だけを選び、選ばれたものだけを計算に使う設計である。35BクラスのMoEは1トークンあたり約3Bのパラメータしか活性化しない。ただし、ここが最も誤解されやすい点だが、減るのは「計算する量」であって「保持しなければならないバイト数」ではない。使わないエキスパートも、どこかには置いておかなければならない。
本記事で扱う論文は、Yu Lin、Yiming Wang、Runyuan Cai、Hanze Liu、Xiaodong Zeng(AutoArk)による「The Other Half of the Memory Wall: Serving 35B MoEs from SSD with Trained Routing Prediction」(arXiv:2609.18063、2026年9月16日公開)である。以下では、メモリの壁の「攻められていない半分」をどう壊すかを、実測値とともに追っていく。
graph TD
A["メモリの壁(memory wall)"] --> B["状態(KVキャッシュ)<br>動的・生成とともに伸びる"]
A --> C["重み<br>静的・学習後に固定"]
B --> B1["MLA / スパースアテンション"]
B --> B2["線形状態モデル(Mamba系)"]
B --> B3["PagedAttention によるページング"]
C --> C1["量子化(GPTQ / AWQ / int4)"]
C --> C2["RAM・CPUへのオフロード"]
C --> C3["SSDからのストリーミング<br>=本記事の主題"]
35B MoEはなぜ24GBのマシンに載らないのか
「35Bクラスのモデルを4bitに量子化すれば、24GBのメモリでも載るのではないか」——この期待は、MoE(Mixture-of-Experts)では裏切られる。理由を先に結論として書くと、MoEが減らすのは1トークンあたりの計算量であって、保持しなければならないバイト数ではないからだ。
具体例として、Qwen3.6-35B-A3Bをベースにした35B級MoEの4bitチェックポイントを分解してみる。
| 項目 | サイズ |
|---|---|
| 4bitチェックポイント全体 | 19.5 GB |
| うちルーティング対象のエキスパート重み | 18 GB(16.9 GiB) |
| 1層あたりのエキスパート重み(int4) | 453 MB |
| 1エキスパートあたり | 1.77 MB |
| 残り(アテンション、埋め込み、共有エキスパート、グループスケール) | 約1.6 GB |
40層 × 256エキスパートという構成では、1層のエキスパート重みだけで453MB(256エキスパート × 1.77MB)になる。これが40層分積み上がって18GB、つまりチェックポイントの大半がルーティング対象のエキスパート重みである。残りの約1.6GBはアテンション、埋め込み、共有エキスパート、そしてint4のグループスケールが占める。
ここで注意したいのは、24GBという数字が「重み専用の予算」ではない点だ。OS、KVキャッシュ、他のプロセスが同じメモリを共有する。常駐させる18GB超は、KVキャッシュとOSに残す余地をほぼゼロにする。実際、後述する実測では、19.5GBの重みを全常駐させた構成が18.2GiBを占有し、システムがページングを始める様子が確認されている。
では、さらに小さい量子化に進めばよいのか。GPTQやAWQといった事後学習量子化の手法が4bitを実用的な作業点に押し下げたが、4bit未満では事後誤差が急増し、事後の回復が難しくなる。量子化は「重みを小さくする」方向の解であり、ここではその下限に近づいている。
MoEのスパース性も救いにはならない。35BクラスのMoEは1トークンあたり約3Bのパラメータしか活性化しないが、これは計算に使う量の話である。使わなかったエキスパートも、次のトークンで選ばれる可能性がある以上、どこかに置いておかなければならない。
「1トークンあたり3Bしか使わないなら、使わない分は別の場所に置いておけばよい」——この素朴な発想が、次に検討すべき選択肢になる。
なぜ素朴なSSDオフロードは効かないのか
「重みはすべてSSDに置いておき、必要なエキスパートだけをその都度読み込めばよい」——これは自然な発想であり、実際に多くのオフロード実装がこの方向を試してきた。しかし結論から書くと、エキスパート重みをSSDに置いて必要時に読むだけでは速くならない。理由はディスクの速度そのものではなく、読み込みを始められる時刻の遅さにある。
MoE(Mixture-of-Experts)では、各層に「ルーター」と呼ばれる小さなネットワークがあり、トークンごとに使うエキスパートを数個だけ選ぶ。このとき問題になるのは、層N+1で使うエキスパートが、層Nの出力(隠れ状態)から計算されたルーティング結果で決まるという依存関係だ。つまり層N+1の重み読み込みを開始できるのは、層Nの計算が完全に終わってからになる。
結果として、推論は「計算 → ルーティング → SSD読み込み待ち → 計算」というサイクルになる。40層構成なら、1トークンを生成するあいだにディスクレイテンシが40回、しかも直列に露出する。
sequenceDiagram
participant L as 層N
participant R as ルーター
participant D as SSD
participant L2 as 層N+1
L->>R: 隠れ状態(トークンt)
R->>R: ルーティング計算
R->>D: 層N+1のエキスパートを要求
Note over D: この時点で初めて読み込み開始<br/>待ち時間がそのまま直列で露出
D-->>L2: 重み到着
L2->>L2: 計算開始
「読み込みを計算の裏に隠せばよい」と考えるのは正しい方向だが、隠すにはまだ存在しない層Nの出力を先に知る必要がある。これが素朴なオフロードが抱える核心的な矛盾である。
先行研究として、Pre-gated MoE(ISCA 2024)がこの矛盾に挑んでいる。同一トークン内で、ブロックNのアテンションが終わった直後にブロックN+1のエキスパートを選ぶ方式だ。ただし層ごとの同期とヘッドの評価がGPUパイプラインを drain させ、本論文の実測では1ステップあたり30〜100msの追加コストが発生し、隠せる読み込み時間を上回ったと報告されている。同一トークン内でリードを取る変種は、いずれも予測なしのLRUベースラインを下回ったという。
| 方式 | リード距離 | 読み込みを開始できる条件 | 実測された代償 |
|---|---|---|---|
| 素のオンデマンド | 0 | 層Nの出力確定後 | ディスクレイテンシが毎層直列で露出 |
| Pre-gated MoE(同一トークン内) | 1層 | ブロックNのアテンション後 | 30〜100ms/step の同期・評価コスト |
| トークンをまたぐ予測 | 1トークン | 前トークンにおける層Nのnorm出力 | 予測の近似コスト(学習側で一括支払い) |
表の3行目が、本論文が取る設計である。リードを「1層」ではなく「1トークン」に伸ばすと、ヘッドの評価が層のクリティカルパスから外れ、SSD読み込みを丸々1デコードステップ分のあいだに重ねられる。この発想を成立させるには、予測を単なるヒントではなく実行経路そのものとして扱う必要があり、その詳細が次の論点になる。
Edge0の全体像:SSD常駐+予測+アダプタの3点セット
ここまでで、24GB級のマシンに35B MoEを常駐させるのが難しいこと、そして素朴なSSDオフロードでは依存関係のせいで読み込み待ちが直列に露出してしまうことを見てきた。Edge0はこの2つに対して、エキスパート重みをSSDに置いたままmmapでオンデマンドにストリーミングし、学習済みの軽量ヘッドで「次に必要なエキスパート」を先読みする推論エンジンとして答えている。
構成要素は3つあり、それぞれが問題の別の側面を引き受ける。
| 構成要素 | 役割 | 決めること |
|---|---|---|
| SSDストリーミング・エキスパート層 | エキスパート重みをディスクに置いたまま必要な分だけ読み込む | ピークメモリを「パラメータ総数」ではなく「活性ワーキングセット」で決める |
| Prerouter(プリルーター) | 次層のルーティングを1トークン先に予測する | 予測を先読みのヒントではなくルーティングそのものとして採用する |
| Recovery LoRA(リカバリーLoRA) | int4量子化とルーティング置換で失った品質を蒸留で回復する | マージせず並列デルタとして適用し、4bitのバイト列に触れない |
1つ目が「重みをどこに置くか」、2つ目が「いつ読むか」、3つ目が「失った品質をどう埋めるか」を担当する。ここでいう活性ワーキングセットとは、ある時点で実際に計算に使われている重みと中間結果の総量のことだ。35Bのチェックポイント19.5GBのうち約18GBを占めるエキスパート重みをディスク側に追い出し、メモリに残すのは活性ワーキングセット、KVキャッシュ、そして42MBのアダプタ程度になる。結果として、19.5GBのモデルが2.9GiBのピーク活性メモリで動く。
ソフトウェア設計の要点は、バックエンドの隔離にある。MLX固有のコードはすべてバックエンドfacade(コア・NN・I/O・量子化の各層)の裏に隠され、モデル仕様、Prerouter、ストリーミングプール、サーバーはfacadeにしか依存しない。CUDAなどの追加バックエンドは同じインターフェースを実装するだけで載る構造だ。モデル自体は単一のMoESpec(エキスパート数、K、ルーティング方式、量子化、重みレイアウト、チャットのキーテンプレート)で記述され、1つの汎用ストリーミング層が両tierを扱う。
flowchart LR
SSD["SSD: int4スタック済みsafetensors<br/>35Bで19.5GB"] --> MMAP[mmap]
MMAP --> POOL["ストリーミングプール<br/>OSページキャッシュ / LRU<br/>固定スロットのダブルバッファ"]
POOL --> DEC["凍結したint4デコーダ"]
DEC --> SUM["和ノード"]
LORA["LoRA分岐<br/>(並列デルタ)"] --> SUM
SUM --> OUT["出力トークン"]
HEAD["層NのPrerouterヘッド"] -.->|層N+1のルーティングを1トークン先読み| POOL
この3点セットは独立した部品ではない。予測が選び、ストリーミング層が積み、アダプタがその結果を回収する、という1つの機構として動く。以下、SSDストリーミング層、Prerouter、Recovery LoRAの順に中身を見ていく。
SSDストリーミング・エキスパート層:4つの実行パス
「重みはディスクに置き、必要な分だけメモリに載せる」という方針を実際の推論エンジンに落とすとき、最初に突き当たるのは「読み込みの形は1つではない」という事実だ。35BクラスのMoEでは、40層×256エキスパートの重みがint4でも1層あたり453MB(1エキスパートあたり1.77MB)、ルーティング対象だけで**18GB(16.9GiB)**に達する。プリフィル(プロンプトを一括処理する段階)とデコード(1トークンずつ生成する段階)では必要なエキスパートの集合がまったく異なり、同じ読み込み手順を使い回すとどちらかが必ず損をする。そこでEdge0は、用途の異なる4つの実行パスを切り替えて使う。
| パス | 読み込みの方式 | 主な用途 |
|---|---|---|
| exact | 重複を排除したオンデマンドのバンドル構築+スタック+量子化gather | 他の全パスの正しさの基準 |
| staged | 固定スロットのダブルバッファリング。ルーティングのインデックスをスロット表に写像し、インデックスをGPUに出さない | デコードの主力 |
| hot | 層ごとのLRUに常駐させたホットエキスパート集合。ヒットはスタック済みgather、ミスはexactへフォールバック | ヒット率重視 |
| whole-layer | 層の全エキスパートを一括ロード。層ごとのスタック済みレイアウトにより256×9回ではなく9回の直接読み込みで済む | プリフィル一括 |
exactは最も素直な実装で、必要になったエキスパートだけをその場で読み、テンソルを組み立てて量子化gatherを呼ぶ。速度は出ないが、常に正しい。stagedはこれをデコード向けに作り替えたもので、層ごとに固定のスロットを確保してダブルバッファリングする。ルーティングのインデックスはスロット表への写像に使われ、GPU側には出ない。同じエキスパート集合が続く間は計算グラフのノードを再利用できるため、永続的なスティッキースロットテンソルをincr_stackでその場更新する——40層分の9つのスタック済みテンソル(gate/up/downの重みとそれぞれのスケール・バイアス)を毎ステップ組み直すのではなく、変化したエキスパートのスロットだけを書き換える。hotは層ごとのLRUにホット集合を常駐させる方式で、PowerInferのホット/コールド分割やMixtral-offloading、MoE-Infinityの人気度キャッシュと同じ系統に属する。whole-layerはプリフィル専用で、層の全エキスパートをまとめて読み込む。層ごとにスタック済みレイアウトになっているため読み込み回数が桁違いに減り、CPU側のロードが前の層のGPU実行と重なる。
ここで重要なのは、どのパスを使っても出力が変わらないという保証だ。すべてのパスがdown(silu(gate(x)) · up(x))を同一の量子化gatherカーネルで計算し、テストスイートが各パスを逆量子化した参照実装と要素単位で比較する。相対L2誤差は1%未満、実測の残差は**約0.24%**で、これはカーネル自体が持つbf16内部精度に由来する。この契約があるからこそ、エンジンは層ごと・フェーズごとにパスを自由に切り替えられる。
flowchart TD
START[フォワードパス開始] --> PHASE{フェーズ}
PHASE -->|プリフィル| WL[whole-layer<br/>層の全エキスパートを一括ロード]
PHASE -->|デコード| DEC[staged<br/>固定スロット+incr_stackでその場更新]
DEC --> HIT{LRUにホット集合あり}
HIT -->|あり| HOT[hot<br/>スタック済みgather]
HIT -->|なし| EX[exact<br/>オンデマンド構築]
WL --> GATHER[量子化gather<br/>down of silu of gate times up]
HOT --> GATHER
EX --> GATHER
GATHER --> SAME[全パス同一の数学契約<br/>相対L2 1%未満]
では、この構成は素の常駐と比べて本当に得なのか。同じMac mini M4 Pro 24GBで、19.5GBの4bit重みをすべて常駐させた素のmlx-lmサーバーは3.9 tok/sで18.2GiBを占有する。一方、Edge0のK=4プロファイルは20.4 tok/sで2.9GiBだ。約5倍速く、占有は6分の1以下になる。なお、この2.9GiBはMLXアロケータが報告するピーク値であり、OSやファイルバックのページまで含めたプロセスの総メモリ量ではない点には注意したい。理由は単純で、常駐させた18.2GiBは回収不能であり、24GBのうちKVキャッシュとOSに残る余地がほとんどないため、システムがページングを始めてしまうからだ。
ただし、これは「オフロードが無条件に優位」という話ではない。Edge0側にもコストはあり、その正体はグラフ構築とテンソル組み立てである。重みの読み込みではなく、エンジン自身の処理だ。完全なルーティング予測と無制限のキャッシュを与えた理想条件では、残るコストは1ステップあたりのテンソル組み立てだけになる。incr_stackによる差分更新は、まさにその残存コストを直接叩きにいった設計だと言える。
Prerouter:予測をそのままルーティングにする
Prerouter(プリルーター)は、Edge0で最も独自性の高い部品だ。ひと言でいえば、次の層がどのエキスパートを使うかを1トークン先に予測する軽量ヘッドであり、その予測を「先読みのヒント」ではなくルーティングの決定そのものとして採用する。ここでいうルーティングとは、MoE層のルーターがトークンごとにどのエキスパートを活性化するか選ぶ処理を指す。通常のオフロード設計では「予測して読み込むが、実際に使うのはルーターが決める」ため、予測が外れた分は無駄な読み込みになる。Prerouterはこの前提をひっくり返す。
なぜリードは「1層」ではなく「1トークン」なのか。 層N+1のエキスパート選択は層Nの出力に依存する。したがって、層N+1の重み読み込みを始めるべき時点では、その選択結果はまだ存在しない。ここで待つと、SSDの読み込みレイテンシが毎ステップ直列に露出してしまう。Prerouterはこれを**二重シフト(dual shift)**で断ち切る。層Nが所有するヘッドが、トークンtにおける層Nのアテンション後のnorm出力を入力として走り、トークンt+1における層N+1のルーティングを予測する。層N+1は1トークン前に作られた予測を消費するため、スロットを埋めるSSD読み込みが現在のフォワードパスと重なる。
同一トークン内で1層先を覗く方式では、層ごとの同期とヘッド評価が必要になり、実測で1ステップあたり30〜100msの追加コストが出た。これは隠せる読み込み時間を上回る。1トークン先にずらすとヘッド評価が層のクリティカルパスから外れ、1ステップ1回のflushで全staged層をまとめて予測できる。開いたウィンドウは丸々1デコードステップに及ぶ必要があり、K=4で1層のエキスパートが約7MB、Prerouterアームでもコールドロード待ちに1ステップあたり101.9msを使うからだ。
ヘッドの構造。 各ヘッドは層ごとに fc1 → erf-gelu → fc2 のMLPと、線形残差パスℓで構成される。残差パスℓは次層のルーター重みでウォームスタートする(既定はゼロ)。これにより学習は「次層のルーターをこの隠れ状態に直接適用する」状態から始まり、MLPがその補正を学ぶという形になる。入力特徴量は隠れ状態に2つのtop-k one-hot(この層がこのトークンで実際に選んだエキスパート、前トークンで選んだエキスパート)を連結したもので、次元は d_model + 2E だ。35Bでは 2048 + 2×256 = 2560(隠れ幅512)、8Bでは 1536 + 2×128 になる。ヘッドはfp16で書き出され、fp32で実行しても測定可能な時間増だけで精度は上がらない。
| 項目 | edge0-35b | edge0-8b |
|---|---|---|
| ヘッド数 | 33(オーナー層6〜38) | 16(オーナー層7〜22) |
| 予測の消費層 | 層7〜38 | 層8〜23 |
| 予測からストリーミングする層の割合 | 40層中32層 | 24層中16層 |
| ヘッド入力次元 | 2560(2048+2×256) | 1536+2×128 |
| 消費プロファイル | K=4、インクリメンタル・スティッキースロットスタック | K=8、層ごと8スロット |
予測=ルーティングの帰結。 デコード時、MoE層はルーターのロジットではなくPrerouterのロジットでルーティングする(元のルーターと同じsoftmax-topk/sigmoid-groupの数学を使う)。ルーティング集合が予測集合になるため、stagedスロットは正確に写像され、落とすエキスパートが存在しない。先行方式が推論時にランタイムで扱っていたカバレッジ対品質のトレードオフは、ここで構造的に消える。近似のコストは推論時の処理から学習側へ移され、一度だけ支払われる。
sequenceDiagram
participant L as 層N
participant H as Prerouterヘッド(層N所有)
participant S as SSD読み込み
participant N1 as 層N+1
L->>H: トークンtのnorm出力を渡す
H->>S: トークンt+1のルーティングを予測して読み込み発行
S-->>N1: 予測済みスロットにエキスパートを配置
Note over S,N1: 読み込みは現在のフォワードパスと重なる
L->>N1: トークンt+1の計算を開始
N1->>N1: 予測ロジットでルーティング(ルーターは使わない)
正直に開示された難点:feature drift。 ヘッドの入力には「その層が実際に実行したルーティング」が含まれる。学習時はベースルーターの選択が入るが、デコード時はヘッド自身の予測が入る。ヘッドはこのズレのために再学習されず、代わりにRecovery LoRAがprerouting込みのstudent pathで学習されて配備時の入力分布を見る。品質測定は、int4量子化+ルーティング近似+このズレをまとめて評価したものだ。なお、プリフィルは両tierともwhole-layerパスを使う。層の全エキスパートがヒットし、予測する対象がないからだ。Prerouterが効くのはデコードの場面であり、同じ学習済みヘッドと同じstager抽象化が、35BのK=4と8BのK=8という異なる消費プロファイルの両方を提供する。
Recovery LoRA:4bitとルーティング置換の代償を蒸留で取り戻す
Edge0が実際に配備するモデルは、(int4ベース) + (ルーティング置換) + (LoRA) という3層構造になっている。ここでいうLoRA(Low-Rank Adaptation)は、凍結した重みの脇に小さな低ランク行列のペアを足すことで振る舞いを調整する仕組みで、ベース本体には一切手を加えない。Recovery LoRA(リカバリーLoRA)は、その名の通り前の2層で失った品質を回収するためだけに存在するアダプタだ。4bitへの量子化とPrerouterによるルーティング置換は、それぞれ単独でも品質を削る。この2つの損失を別々に埋めるのではなく、まとめて取り戻すのがこの部品の役割になる。
学習はstudent pathで行う。 アダプタは、Prerouterが実際に選んだルーティングを使った完全なフォワードパス(student path)で、教師データに対する交差エントロピーを最小化するように学習される。つまり「元のルーターで動かしたときの正解」ではなく、配備時に実際に通る経路の上で教師を模倣する。結果としてこのアダプタは、量子化誤差とルーティング近似を同時に補償するものになる。35B tierではランクr=16・α=32という小さな設定で、アダプタ本体は42MBに収まる。
最重要の設計判断は、アダプタをベースにマージしないことだ。 計算は y = W_int4(x) + (α/r)·BA·x という並列デルタとして実行する。W_int4(x) は4bit重みのままの計算、右辺第2項がLoRA分岐であり、この2つを足し合わせるだけで4bitベースのバイト列には一切触れない。ところが「マージしてから逆量子化し、再量子化して保存する」という素朴な実装は失敗する。これは実装の問題ではなく算術の問題だ。LoRAデルタのRMSは10⁻³程度で、4bitのグループスケールの刻み幅を下回るため、再量子化の段階で重みレベルのデルタがほとんど消えてしまう。
| 評価レベル | マージ+再量子化で残る効果 |
|---|---|
| アテンション射影 | 34% |
| dense射影 | 2% |
| ロジットレベル | 18% |
一方、非マージ経路が追加で支払うコストは、アダプタ重みの42MBと測定不能なほど小さなデコード時間増だけであり、忠実さは厳密に非マージ側が上になる。「マージして軽くする」という選択肢は、ここでは速度のためではなく精度を捨てるための選択になってしまう。
flowchart LR
A[int4ベース W_int4] --> B[量子化重みのまま計算]
L[Recovery LoRA BA] --> C["(α/r)·BA·x を並列に計算"]
B --> D[和ノードで合成]
C --> D
D --> E[出力]
F[int4ベース + LoRAをマージ] --> G[逆量子化 → 再量子化]
G --> H["デルタの大半が消える<br/>attn 34% / dense 2% / logit 18%"]
運用上の含意:1つの読み取り専用ベースが、複数世代のアダプタを同時に提供できる。 ルーティング幅Kや学習データを変えたtierを用意するとき、ベースの再学習は不要で、差し替えるのはアダプタとヘッドの2ファイルだけで済む。この性質が、次に述べる学習レシピの反復コストを大きく下げることになる。なお「凍結ベース+サイドカー」というパターン自体はQLoRAやMultiLoRAが既に開いた系譜であり、本論文の貢献はパターンの採用ではなく、配備時にアダプタが何を生き延びるかを定量的に問うた点にある。
学習レシピ:3フェーズと、そこで学ばれた非自明な知見
配備されるモデルは (int4ベース) + (ルーティング置換) + (非マージLoRA) という3層構造であり、品質の大半は「何を学習するか」ではなく「どの順序で何を学習するか」で決まる。Edge0の学習は3つのフェーズに分かれており、それぞれ学習対象も損失もデータ量も異なる。
全フェーズに共通する前提が1つある。 学習は、4bitの配備チェックポイントを逆量子化して作ったbf16の再構成の上で走る。つまり「配備されるものを学習する」という原則であり、量子化誤差を後から別の重みで埋め合わせるのではなく、最初から誤差を含んだ重みの上でアダプタを育てる。ベース本体は全フェーズを通して凍結される。
| フェーズ | 学習対象 | 損失 | データ量 |
|---|---|---|---|
| Phase 1:ヘッド蒸留 | Prerouterヘッドのみ | 次層の真のルーターの模倣 | ― |
| Phase 2:student pathでのSFT | LoRA(アテンション・線形アテンション・共有エキスパートの射影) | 教師テキストへの交差エントロピー | 約200万行 |
| Phase 3:オンポリシー蒸留 | LoRA(Phase 2と同じ面) | reverse KL(top-k+tail項) | 約20万行(SFTの1/10) |
Phase 1はPrerouterヘッドだけを学習する。 損失は次層の真のルーター出力を模倣するものであり、ヘッドは「テキストに適合する」のではなく「ルーティングを予測する」ことを学ぶ。ここで重要なのは、Phase 1を終えただけでは配備に使えないという点だ。ヘッドだけを蒸留したチェックポイントをstudentルーティング(予測をルーティングとして採用する経路)で走らせると、出力は反復的で崩壊したテキストになる。同じヘッドにPhase 2のSFTを加えると、速度もメモリも変えずに整合的な出力に変わる。ルーティング置換を「使えるもの」にしているのはヘッドの予測精度ではなく、配備時の入力分布で学習したアダプタだというのが、この実験から読み取れる最も実用的な知見になる。
Phase 2はstudent pathでのSFT(SFT=教師が用意した模範テキストで次トークン予測を訓練する段階)であり、Prerouterルーティングを有効にした完全なフォワードパスを走らせる。LoRAはアテンション、線形アテンション、共有エキスパートの射影に付くが、ルーティング対象のエキスパートには付けない。エキスパート重みはSSDからストリーミングされ、ルーティング幅を変えた別tierとして差し替え可能であり続ける必要があるためだ。学習データは教師生成テキスト約200万行。
順序は交渉不可だったと報告されている。ヘッド蒸留 → SFT → オンポリシー蒸留の順でなければならず、SFTを先に置くとSFTの信号がヘッドの微小な勾配を飲み込んでしまう。また、目的関数の設計思想も非自明だ。ルーター一致率を追い込むのは間違った目的とされている。前トークンの隠れ状態から次層のルーティングを当てるというクロストークン予測には情報量の限界があり、問うべきはstudentルーティングの下で言語モデルとして良いテキストを出せるかどうかである。
Phase 3のオンポリシー蒸留は、Phase 2のチェックポイント自身に生成させ、元のfp16ベースが教師としてトークンを採点し、配備経路を通して勾配を取る。「オンポリシー」とは、教師が用意した固定データではなく、studentが実際に生成した分布の上で学ぶことを指す。目的関数はreverse KL(mode-seeking。studentに教師の支持全体を覆わせず、教師が確信しているモードに寄せる)で、教師のtop-kトークンに適用し、top-k外の確率質量は捨てずにtail項で運ぶ。SFTの1/10にあたる約20万行で収束する。
flowchart TD
P0["4bit配備チェックポイントの逆量子化bf16再構成<br/>ベースは常時凍結"] --> P1
P1["Phase 1: ヘッド蒸留<br/>学習対象=Prerouterヘッドのみ"] --> P2
P2["Phase 2: student pathでのSFT<br/>学習対象=LoRA(約200万行)"] --> P3
P3["Phase 3: オンポリシー蒸留<br/>reverse KL+tail項(約20万行)"] --> P4
P4["配備: int4ベース + ルーティング置換 + 非マージLoRA"]
このレシピの実務的な価値は反復コストにある。 ルーティング幅Kを変えたtierを用意するとき、ベースは凍結されているので再学習が必要なのはアダプタとヘッドだけであり、作業はファイルの差し替えに帰着する。これが、ベースモデルが既定で持つK=8ではなくK=4で35Bをリリースできた理由だ。16GBマシンでの同一セッションA/Bでは、K=8からK=4にすることでデコードが3.3 → 6.4 tok/sとほぼ倍増し、ピークメモリも下がり、再学習したtierは品質表を維持した。ルーティング幅は速度とメモリを同時に動かす唯一のノブであり、ベースが凍結されていることが、これを「学習キャンペーン」ではなく「ファイル差し替え」に変えている。
実測:速度・メモリ・品質をどう読むか
公開されている2つのtierについて、速度・メモリ・品質の数値を測定条件とセットで整理する。数値そのものより「どの条件で測ったのか」を押さえることが、この構成を自分の環境に当てはめられるかどうかを判断する鍵になる。
測定条件
- tier別の数値(35B/8B)は Mac mini M4 Pro 24GB で取得されている。
- Prerouterの有無によるA/Bは MacBook M2 16GB で行われている。18.4GiBのチェックポイントが物理的に載らないマシンで、毎ステップSSDからエキスパートをフォールトインさせる条件だ。
- 各アームは別プロセスでウォームし、アーム順を毎ラウンド回転させ、反復実行の中央値を報告している。
- 単発のベンチマークは同一ハードでも**±40%、16GB機ではセッション間で最大2.3倍**ばらつく。そのためセッションをまたぐ数値は証拠として使われていない。自分の環境で測るときも、この振れ幅を前提にしたほうがよい。
速度とメモリ
| 項目 | edge0-35b | edge0-8b |
|---|---|---|
| デコード | 20.4 tok/s | 28.0 tok/s |
| ピーク活性メモリ | 2.9 GiB | 1.5 GiB |
| ディスク上チェックポイント | 19.5 GB | 4.5 GB |
| プリフィル コールド/ウォーム | 113 / 140 tok/s | 500 / 1102 tok/s |
| ルーティング幅 K | 4 | 8 |
ここで注意したいのは、2.9GiBはMLXアロケータのピーク活性メモリであり、プロセスのRAM総使用量ではないという点だ。19.5GBの重みはディスク上にあり、必要になった分だけが読み込まれる。参考として、同じ重みを全常駐させると18.2GiBを占有して3.9 tok/sに落ちる。回収不能な重みが24GBの大半を占め、KVキャッシュとOSに余地が残らないためだ。つまりストリーミング構成は常駐構成の1/6〜1/7の占有で約5倍速い。
Prerouterは何を買っているのか
重みが載らない16GB機での比較は次のとおり。
| K | オンデマンド (tok/s) | Prerouter (tok/s) | 改善 |
|---|---|---|---|
| 2 | 4.8 | 8.6 | +80% |
| 4 | 3.5 | 6.4 | +82% |
| 8 | 1.8 | 3.3 | +84% |
メインスレッドがエキスパート読み込みでブロックされる時間は、K=2で154.9→46.5ms/step、K=4で244.0→101.9ms/step、K=8で575.0→211.6ms/stepへ減る。この間、ディスク読みはステップの最大12%、プロセスは8コアのうち約1コア、GPUは**35〜41%**しか使われていない。ボトルネックは資源の飽和ではなく、直列化した読み込みレイテンシであり、それをクリティカルパスから外すことが予測器の買っているものだ。
重要なのは、これが予測精度の話ではなく保存則だという点である。両アームの1ステップあたり読み込みバイト数はほぼ同じ(K=8で125.1 対 126.9MiB)。読みを外に移すことはできても、消すことはできない。だからどれだけ稼げるかはストレージ層の性質で決まり、ヘッドは正しい集合を供給すればよい。
代償はページキャッシュだ。先読みには予測エキスパートが必要になる前に常駐している必要があり、常駐メモリは回収できない。K=8ではPrerouter側が1.43GiB多く常駐し、ページキャッシュを1.15GiB失う。ロードコストは実測で 1.17ms + 1.33ms × コールド率 にフィットし、先読みはロードあたりでは安く、バイトあたりでは高い。
品質
OpenCompassによる同一設定の比較(満点100)。
| ベンチマーク | edge0-35b (int4) | Qwen3.6-35B-A3B (fp16) | edge0-8b (int4) | Ling 3.0 tiny (fp16) |
|---|---|---|---|---|
| AIME 2026 | 86.6 | 92.7 | 63.3 | 73.3 |
| HumanEval | 90.9 | 95.1 | 91.5 | 92.7 |
| GPQA-Diamond | 79.8 | 81.8 | 70.7 | 71.2 |
| MMLU-Pro | 81.0 | 84.6 | 70.1 | 65.8 |
| IFBench | 57.9 | 61.7 | 53.9 | 60.6 |
| 平均 | 79.2 | 83.2 | 69.9 | 72.7 |
読み方のポイントは3つある。第一に、平均差は35Bで3.9点、8Bで2.8点であり、int4量子化とルーティング置換の損失の大半が回収されている。第二に、8BのMMLU-Proは70.1 対 65.8とベースを上回る逆転が起きている。第三に、劣化はタスク間で一様ではなく、8BのAIME 2026は63.3 対 73.3で10点落ちる。長い推論の連鎖が、量子化とルーティング近似が最も可視化される場所だ。
なお、これらの品質スコアは提供元自身による自己公表値であり、第三者検証ではない。ベンチマーク手順は公開されているので、Apple Siliconユーザーは自分で追試できる。
プリフィルは鏡像の挙動を示す。コールドの初回プリフィルはSSDのフォールトインを払うが、whole-layerロードとページキャッシュが効いたウォーム時は計算律速になる。つまり制約されているのはデコードであり、プリフィルではない。この構成のすべての機構がデコードを狙っているのはそのためだ。
既存手法との位置づけ:「重みを移す」から「重みをどこに置くか決める」へ
エキスパート・オフロードは新しいテーマではない。GPUメモリに収まらない重みをCPUのRAMやディスクへ逃がし、必要な分だけ戻す研究は長く積み重ねられてきた。それらを「重みをどこに置くか」「先読みするか」「予測が実行経路そのものか」の3軸で並べると、本手法の差分が見える。
| 手法 | 重みの置き場所 | 先読み | 予測は実行経路か |
|---|---|---|---|
llama.cpp --n-cpu-moe |
GPU VRAM+システムRAM(層単位の静的割り当て) | なし(OSページキャッシュ任せ) | — |
| PowerInfer | GPU(ホットニューロン)+CPU(コールド) | 活性化予測器あり | ニューロン選択に使用 |
| Mixtral-offloading / MoE-Infinity | GPU+CPU(人気度・再利用距離ベースのキャッシュ) | キャッシュ戦略 | — |
| FlexGen | GPU/CPU/ディスク(重みとKV) | スケジューリング | — |
| LLM in a Flash | フラッシュ(オンデマンド読込) | アクティベーション予測 | 読込のスケジューリング |
| Pre-gated MoE | メモリ階層(CPU/GPU) | 同一トークン内で1層先 | 部分的(実行時にフォールバック/ドロップ) |
| 本手法 | SSD(オンデマンド読込) | 1トークン先 | 予測=ルーティング(落とすものがゼロ) |
flowchart LR
A["静的配置<br/>GPU VRAM+RAM<br/>(llama.cpp --n-cpu-moe)"] --> B["キャッシュ配置<br/>人気度・再利用距離<br/>(Mixtral-offloading / MoE-Infinity)"]
B --> C["階層拡張<br/>ディスクまで含む<br/>(FlexGen / LLM in a Flash)"]
C --> D["先読みの導入<br/>同一トークン内で1層先<br/>(Pre-gated MoE)"]
D --> E["予測=ルーティング<br/>1トークン先・落とすものなし<br/>(本手法)"]
これらに共通するのは、フットプリントを「移す」だけという点だ。重みの総量は依然として数十GBを占め、ディスクに置いた分については「次のステップがどのエキスパートを必要とするか」を知らないまま読む。だから読み込みの待ち時間が計算の直列に露出する。
本手法の差分は3点に整理できる。第一に、層単位の静的割り当てではなく必要に応じたSSDからの動的ストリーミングであること。第二に、OSのページキャッシュ任せではなく学習済みのヘッドがルーティングを1トークン先読みすること。第三に、予測をヒントではなくルーティングそのものとして採用するため予測集合=実行集合となり、落とすエキスパートが構造的に存在しないことだ。先行のPre-gated MoEが実行時にカバレッジと品質を天秤にかける必要があったのに対し、この設計ではそのトレードオフ自体が消える。
直交する技術との関係も整理しておきたい。KVキャッシュの圧縮(MLA、スパースアテンション、PagedAttentionなど)はメモリ予算を増やす別方向の技術で、本手法はその恩恵を受ける。量子化側ではGPTQやAWQ級の手法が4bitを実用上の作業点にしたが、本手法はint4を採用して損失を率直に受け入れ、蒸留で回収する。QLoRA以来の「4bitベース+アダプタ」の系譜に連なり、そのうえでアダプタが配備時に何を生き延びるかを問うている。
つまり従来は「限られた場所へ重みをどう移すか」が問題だった。本手法はその問いを**「どの重みをどこに置き、いつ読むかを誰が決めるか」**へ置き換えた点に位置づけられる。
限界と注意点:論文が自ら認めていることと、報道とのギャップ
ここまでの数字は魅力的だが、そのまま受け取るのは危険だ。この論文は自ら5つの限界を明示しており、さらに発表時のデモと、実際に公開コードで再現できる範囲の間には無視できない隔たりがある。導入を検討するなら、この2つを最初に確認しておきたい。
論文が明示する限界
- 単一リクエストのみ、FIFO直列化。並行リクエストの処理はサービング層の責務とされ、エンジン自身は扱わない。バッチングはエキスパートのワーキングセットを変えてしまうため、1リクエスト単位のプロファイルではモデル化していない。
- ストリーミングエンジンのデコードはCPU側がボトルネックであり、ストレージ側ではない。40層フォワード中のグラフ構築に1ステップ44msという下限があり、これはストレージ側の最適化では動かせない。埋めるにはカーネルレベルでのグラフ償却か、より小さなモデルが必要になる。
- Prerouterの利得は「取り除く待ち時間」に比例する。ホットキャッシュや高速ストレージでは利得が縮み、上限は再利用で決まる。隣接トークンが1層のエキスパート集合で一致するのは約4分の1にすぎず、先読みしたエキスパートの多くは一度読まれて二度と使われない。代償はページキャッシュから奪う回収不能な常駐メモリであり、メモリが厳しくストレージが遅い領域でこそ勝つ。これはまさにこの設計が狙った領域だ。
- 品質損失は長鎖推論に集中する。AIMEでは35Bが6.1点、8Bが10.0点落ちる。他の8Bベンチは6.7点以内で、MMLU-Proではむしろstudent側が4.3点上回る。Recovery LoRAはほぼすべてのタスクで損失を回収するが、推論の連鎖はint4とルーティング置換の影響が最も可視化される場所だ。
- MLXバックエンドが唯一の実装。バックエンドの抽象化層は用意されているが、CUDA用のスロットはあくまでアーキテクチャ上の受け口であり、動くコードではない。
再現できる範囲と、まだ公開されていない範囲
発表時に最も注目を集めたのは「iPhoneで35Bが動く」というデモだ。ピークメモリ1〜2.5GB、クラウド接続も外部GPUもなしで35Bが動作する映像が示された。しかし、公開リポジトリがサポートするのはmacOS+Apple Silicon(MLXバックエンド、M1〜M4でテスト)のみであり、iOSビルドもiPhone用のベンチマーク手順も公開されていない。公開ベンチの2.9GiBは**Mac mini M4 Pro(24GB)**での測定値である。
| 項目 | 今すぐ再現できる範囲 | 未公開・未検証の範囲 |
|---|---|---|
| 対応プラットフォーム | macOS+Apple Silicon(M1〜M4、MLX) | iOSビルド、CUDAバックエンド |
| 速度・メモリ | Mac mini M4 Pro 24GBでの20.4 tok/s・2.9GiB | iPhoneでの1〜2.5GBデモ |
| ベンチマーク | examples/bench.pyで追試可能 |
iPhone向けベンチ手順 |
| 品質スコア | OpenCompassの自己公表値 | 第三者による独立検証 |
「phone-class memory」という表現と、開発者が今日iPhoneで動かせるかどうかは別問題だ。iPhoneの数値を再現するにはモバイルバックエンドの公開に加え、速度・発熱・ストレージトラフィック・メモリ圧迫をデバイス別に測定する必要がある。なお、ストレージを backing にした推論そのものは確立した領域であり、フラッシュからパラメータを読む手法や、iPhoneのストレージからスパースエキスパートをストリーミングする実装が先行して存在する。この手法はゼロからの発明ではなく、その系譜のうえに予測とストリーミングを統合したものと位置づけるのが正確だ。
モデルカードが示すもう一つの留保
最後に、モデルカードはこのチェックポイントがプレビュー段階であり、エージェント能力(ツール使用・多段計画・長期の自律実行)はまだ最適化されていないと明示している。ローカルLLMをコーディングエージェントのバックエンドとして使いたい読者にとっては決定的な留保になる。速度とメモリの数字が用途に合っていても、エージェント用途では現時点で推奨できない点は、導入判断の前に必ず押さえておきたい。
自宅サーバー/ローカルLLM運用にとって何を意味するか
自宅のマシンでAIをどこまで動かせるか、という問いに対して、これまで実質的な答えは2つしかなかった。**重みを小さくする(量子化)**か、そもそも小さいモデルを選ぶかである。本手法はこのどちらでもない第三の方向、重みの置き場所を変えるを提示する。能力の上限を保ったままメモリ予算を下げられる点が、最初の2つとの決定的な違いになる。
| アプローチ | やること | 代償・依存するもの |
|---|---|---|
| 重みを小さくする(量子化) | 4bit/低ビット化でフットプリントを削る | 品質とのトレードオフが4bit付近で底を打つ |
| モデルを小さくする(軽量モデル) | パラメータ数の少ないモデルを選ぶ | 能力の上限そのものが下がる |
| 置き場所を変える(ストレージ階層化) | 重みをSSDに置き、必要分だけ読む | ストレージ速度とキャッシュ効率に強く依存する |
今すぐ試せるかどうか
- 今すぐ試せる人:Apple Silicon Mac(M1〜M4)のユーザー。Python 3.10以上、MLX(mlx 0.30.6系)、SSDの空き容量(35Bで約23GB、8Bで約4.2GB)が条件になる。公開されているベンチスクリプトを使えば、公式の数値を自分の環境で追試できる。エキスパートは毎ステップ読み出されるため、内蔵NVMeの利用が現実的だ。
- 待つべき人:iPhone/iPadユーザー(iOSバックエンドが未公開)、NVIDIA GPUユーザー(CUDAは受け口が用意されているだけで動くコードではない)、エージェントやツール呼び出しを主用途にする人(プレビュー段階でありエージェント能力は未最適化)。Windows環境には、MoEエキスパートをシステムRAM側に置く既存のオフロード手段がある。
「ピーク活性メモリ2.9GiB」の正しい読み方
この数字はモデル計算に使われるMLXアロケータのピーク値であり、RAMの総使用量ではない。エキスパート重みはSSDからストリーミングされ、常駐しない。OSやページキャッシュ、KVキャッシュは別枠で必要で、長いコンテキストではKVキャッシュが効いてくる(8Bで3.3kトークンなら全体で約3.3GiB)。実機ではOS・トークナイザ・KV増分のヘッドルームを上乗せして設計したい。
速度の読み方
35Bのデコードは測定条件により14.9〜17.7 tok/s(最新のペア測定では20.4 tok/s)で、人間が読むチャット用途なら概ね許容範囲だが、対話的なコーディング支援では遅く感じられる可能性がある。8Bの23.9〜25.3 tok/s(同28.0)は実用的な水準だ。プリフィルは35Bでコールド113/ウォーム140 tok/s、8Bで500/1102 tok/sと、ページキャッシュの有無で大きく変わる。実運用では「初回リクエストは遅く、2回目以降は速い」という挙動になる。
より広い文脈での位置づけ
オンデバイスAIではNPU性能の向上と統合メモリの大容量化が進んでいる。ストレージ階層化は、その流れとは別の角度から「メモリに載らないモデルをどう動かすか」に答えるもので、Apple Siliconのユニファイドメモリ増強路線とは補完関係にある。ビジネス面では、ベースにApache-2.0のオープンウェイトモデルを使い、差別化を推論フレームワークと学習済みアダプタの層で行う構図が見える。モデル学習の資本競争に直接参加せず、既存モデルを消費者デバイスで動かす技術に価値を置く戦略だ。
flowchart TD
Q["自宅で大きめのモデルを動かしたい"] --> A{"重みはメモリに載るか"}
A -- "載る" --> B["量子化+通常の推論で十分<br/>オフロードは速度を落とす"]
A -- "載らない" --> C{"ストレージは高速か"}
C -- "NVMe SSD・再利用が効く" --> D["ストレージ階層化が有効<br/>予算は活性集合で決まる"]
C -- "低速・再利用が乏しい" --> E["利得は限定的<br/>量子化か軽量モデルへ"]
要点はこうだ。自宅サーバーで大きなモデルを動かすとき、制約は「重みを全部メモリに置けるか」ではなく「活性する分とKVキャッシュが収まるか」に読み替えられる。その読み替えが成立する条件は、ストレージの速度と再利用の度合いであり、モデルの賢さではない。
よくある質問
ここでは、35BクラスのMoEをSSDからストリーミングする推論エンジン(Edge0)について、導入時に最も疑問が集まる5点をまとめる。
「アクティブメモリ2.9GiB」とは、RAMの総使用量が2.9GBということですか?
いいえ、これはモデル計算に使われるMLXアロケータのピーク値であり、マシンのRAM総使用量ではない。エキスパート重みはSSDからmmapでオンデマンドに読み出され、メモリに常駐しないため、この値には含まれない。OS、ページキャッシュ、KVキャッシュは別枠で必要で、コンテキストが長くなるほどKVキャッシュが効いてくる(8B tierで約3.3kトークンなら全体で約3.3GiB)。実機で見積もる際は、OS・トークナイザ・KV増分のヘッドルームを上乗せして設計したい。
Prerouter(ルーティング予測)の「予測をそのままルーティングにする」とはどういう意味ですか?
通常の先読みは「予測して読み込むが、実際に使うエキスパートはルーターが決める」という分担になるため、予測が外れると読み込んだ重みが無駄になり、必要だった重みが間に合わない。本エンジンはデコード時、MoE層のルーティング自体をPrerouterのロジットで決定する(softmax-topk/sigmoid-groupの数学は元のルーターと同一)。その結果、ステージングされたエキスパート集合=実際に使われる集合が構造的に一致し、落とされるエキスパートが存在しなくなる。近似のコストは推論時のランタイム処理ではなく、学習側で一度だけ支払う設計だ。
量子化による精度劣化はどれくらいで、どう補償していますか?
公式のOpenCompass比較では、35B(int4+アダプタ+Prerouterルーティング)はfp16ベース比で平均3.9点の劣化(79.2対83.2)、8Bは2.8点(69.9対72.7)に収まる。
| ベンチマーク | edge0-35b (int4) | fp16ベース | edge0-8b (int4) | fp16ベース |
|---|---|---|---|---|
| AIME 2026 | 86.6 | 92.7 | 63.3 | 73.3 |
| HumanEval | 90.9 | 95.1 | 91.5 | 92.7 |
| GPQA-Diamond | 79.8 | 81.8 | 70.7 | 71.2 |
| MMLU-Pro | 81.0 | 84.6 | 70.1 | 65.8 |
| IFBench | 57.9 | 61.7 | 53.9 | 60.6 |
8BのMMLU-Proでは70.1対65.8とベースを上回る逆転がある一方、8BのAIME 2026は63.3対73.3と10点の劣化があり、タスクごとの差は一様ではない(長鎖の推論で差が開きやすい)。補償はRecovery LoRAで行う。int4ベースを凍結したまま、fp16教師から蒸留したLoRAアダプタを重ねる仕組みで、マージせず並列デルタとして適用するのが要点だ。マージして再量子化すると、アダプタの効果の大半が消える(dense射影ではわずか2%しか残らない)。
llama.cppのMoEオフロードと何が違うのですか?
llama.cppの--n-cpu-moeや--override-tensor "exps=CPU"は「どの層のエキスパートをシステムRAM側に置くか」という静的な配置制御であり、予測器は持たずOSのページキャッシュ任せになる。本エンジンはmmapによる動的なストリーミングに加えて、次層のルーティングを1トークン先読みする学習済みPrerouterを持ち、SSD読み込みを現在のフォワードパスと重ねる。ただし速度はストレージ速度・キャッシュヒット率・ルーティング幅Kに強く依存し、「モデルがVRAMに載るならオフロードは速度を落とす」という原則はここでも変わらない。
発表されている「iPhoneで35Bが動く」は今すぐ試せますか?
現時点では試せない。公開リポジトリがサポートするのはmacOS+Apple Silicon(MLXバックエンド、M1〜M4)のみで、iOSビルドもiPhone用のベンチマーク手順も公開されていない。公表されている2.9GiBはMac mini M4 Pro(24GB)での測定値であり、iPhoneデモの1〜2.5GBは発表時のデモンストレーションであって、再現可能なリリースには含まれていない。モバイルバックエンドの公開と、速度・発熱・ストレージトラフィック・メモリ圧迫のデバイス別測定を待つ段階だ。なおコーディングエージェントのローカルバックエンドとしては、モデルカードがプレビュー段階でありエージェントタスク(ツール使用・多段計画・長期自律実行)に最適化されていないと明示しているため、現時点では推奨しにくい。
まとめ
メモリの壁は、性質の異なる2つの半分に分かれている。生成とともに伸びる動的な状態(KVキャッシュ)の側は、MLAやスパースアテンションといった圧縮技術で大きく前進した。これに対し、学習後に固定される静的な重みの側は、数十GBという容量を前に「データセンターに置く」以外の答えが長く存在しなかった。ここで提示された答えは、重みを小さくすることではなく、重みをどこに置くかを変えることである。
仕組みの核心は3点に集約される。
- エキスパート重みをSSDに置き、mmap(メモリマップトファイル)でオンデマンドにストリーミングする。ピークメモリは「活性集合」で決まり、パラメータ総数では決まらない。
- 学習済みのPrerouter(事前ルーター)が層N+1のルーティングを1トークン先読みし、その予測をルーティングそのものとして採用する。予測集合と実行集合が定義上一致するため、読み込んだのに使われないエキスパートも、必要なのに間に合わないエキスパートも構造的に生じない。
- 凍結したint4ベースに対し、マージしないRecovery LoRAを並列デルタとして重ねる。量子化とルーティング置換の代償は、推論時のランタイム調整ではなく学習側で一度だけ支払われる。
結果として得られる構成は次のとおりである。
| 項目 | edge0-35b | edge0-8b |
|---|---|---|
| デコード速度 | 20.4 tok/s | 28.0 tok/s |
| ピーク活性メモリ | 2.9 GiB | 1.5 GiB |
| ディスク上チェックポイント | 19.5 GB | 4.5 GB |
| fp16教師との品質差(平均) | 3.9点 | 2.8点 |
測定機はMac mini M4 Pro(24GB)である。同じ重みを全常駐させた構成が3.9 tok/s・18.2GiBだったことと比べると、約5倍の速度を6分の1から7分の1のメモリ占有で実現している。ただし2.9GiBはモデル計算に使うMLXアロケータのピーク値であり、OS・ページキャッシュ・KVキャッシュは別枠で必要になる点は読み違えないようにしたい。品質スコアは提供元による測定値で、第三者検証ではない。フレームワーク、チェックポイント、アダプタはいずれもApache-2.0で公開されている。
見落とせないのは、利得の源泉が「予測精度」ではなくクリティカルパスから読み込み時間を外したことだという点である。1ステップあたりの読み込みバイト数は両構成でほぼ同じで、隠せても消せてはいない。ゆえに、どれだけ稼げるかはストレージ層の性質とエキスパートの再利用で決まり、ホットキャッシュと高速ストレージが揃った環境では利得が縮む。この限界も同時に正直に示されている。
現時点で再現できるのはmacOS+Apple Silicon上でのストレージ・ストリーミング推論までである。モバイル環境での動作は今後のバックエンド公開を待つ段階にあり、エージェント用途も未最適化だと明示されている。それでも「手元のマシンはもう十分大きい。重みに必要なのは置き場所だけだった」という結論は、自宅のサーバーでAIを動かそうとする試みに、量子化とは別の方向を示している。
参考資料
- 論文(arXiv): The Other Half of the Memory Wall: Serving 35B MoEs from SSD with Trained Routing Prediction — https://arxiv.org/abs/2609.18063
- 論文 HTML 全文(v1): https://arxiv.org/html/2609.18063v1
- GitHub: Edge0-AI/Edge0(Apache-2.0)— https://github.com/Edge0-AI/Edge0
- Hugging Face: Edge0/Edge0-35B-A3B-preview — https://huggingface.co/Edge0/Edge0-35B-A3B-preview
- Hugging Face: Edge0/Edge0-8B-A1B-preview — https://huggingface.co/Edge0/Edge0-8B-A1B-preview
- 公式モデルページ: Edge0: 35B Models in 3 GB of Memory — https://edge0.ai/models/edge0-35b
- 公式サイト: https://edge0.ai/
- runtimewire「Edge0 open-sources a 35B model runtime that streams weights from storage」— https://runtimewire.com/article/edge0-open-source-35b-model-streaming-inference
- labmemo「Edge0徹底解説」— https://labmemo.com/edge0-streaming-moe-ssd-offload-prerouter-on-device-2026/
- AI Chronicle「Edge0公開。35BモデルがiPhoneで動作、SSD offloadで1〜2.5GBに」— https://ai-chronicle.blog.jp/archives/14883765.html
- Apple Machine Learning Research「LLM in a Flash」— https://machinelearning.apple.com/research/efficient-large-language
- PowerInfer(arXiv:2312.12456)— https://arxiv.org/abs/2312.12456
- Hugging Face Blog「Performant local mixture-of-experts CPU inference with GPU acceleration in llama.cpp」— https://huggingface.co/blog/Doctor-Shotgun/llamacpp-moe-offload-guide
- llama.cpp PR「llama : add --n-cpu-moe option」— https://github.com/ggml-org/llama.cpp/pull/15077
- Qwen「Qwen3.6-35B-A3B」モデルカード — https://huggingface.co/Qwen/Qwen3.6-35B-A3B
- InclusionAI「Ling-3.0-tiny」モデルカード — https://huggingface.co/inclusionAI/Ling-3.0-tiny-base