前回の記事 で、Claude Code の利用制限の逃げ道としてローカルLLMを試し、スワップ地獄に沈んだ話を書きました。結論は「クラウドの代替にはならない」でした。

今回は同じ話の続きです。ただし、使う機械は前回とまったく同じ M1 Pro / 16GB です。

試したのは TurboFieldfare という推論エンジンで、発想が変わっています。 メモリに載らないモデルを、載せずに動かす。 MoE(Mixture of Experts)モデルの共有部分だけを常駐させ、残りの expert はトークンごとに SSD から読み出す、という作りです1

動かしたモデルは Gemma 4 26B-A4B の4bit量子化版です。インストール後の実測サイズは 14,291,915,755 バイト、37ファイルでした。なお「26B」は製品名で、公式のモデルカード上は総パラメータ 25.2B、トークンあたりのアクティブパラメータは 3.8B、128 experts に shared expert が1つ、という構成です。

前回の結論が変わるのか。同じ機械で確かめました。

先に結論:制約が置き換わっただけだった

メモリの約束は守られました。

peak RSS の実測値は 1,521.5〜1,551.3 MB です。26Bクラスのモデルを動かして、常駐がこれだけで済んでいます。

そして vm.swapusage の used は、 計装をつけて記録した4ランすべてで増加しませんでした1061.38 → 1053.38 MB と、むしろ 8MB 減っています)。前回スワップで沈んだのと同じ機械です。

ただし正確に書いておくと、 約1,053MB のスワップは最初から最後まで存在し続けています。消えたのは「増加」であって、スワップそのものではありません。また swap を記録できたのは4ランだけで、後述する低速フェーズと連続負荷のログには swap の記録がありません。ですので言えるのは「記録した4時点では増加しなかった」までです。

速度の安定性は裏切られました。

入力条件を揃えた16ラン(同じプロンプト、prefill 61トークン、生成511トークン、stop=endOfTurn がすべて一致)で、 4.298〜11.884 tok/s という結果になりました。最速と最遅で 2.77倍 です。

ここで「同一条件」と書きたくなるのですが、一致しているのは 推論への入力 であって、システムの状態ではありません。そしてまさにそこが今回の主題になります。

この二つを並べると、この技術の性格が見えてきます。 これは速くする技術ではなく、RAM を買い戻す技術でした。 作者を名乗るユーザーは、自身の M5 Mac Pro で「同じモデルを MLX で動かすと 75 tok/s で RAM 14GB、このエンジンなら RAM 2GB で 31〜35 tok/s」と報告し、If you need maximum speed and flexibility just use MLX と書いています2。速度を差し出して RAM を受け取る取引です。

セットアップでつまずいた3点

README に書かれていない、あるいは書かれていることと違った点が3つありました。

Xcode は(少なくともこの構成では)要りませんでした

README の Requirements には Xcode 26 and Swift 6.2 or newer とあります1。数十GBの Xcode を入れる前提だと思って、ここで離脱する人がいそうです。

ところが私の環境には Xcode.app がなく、Command Line Tools 26.6.0 だけでした。それでも swift build -c release146.44秒で成功 しました。

理由はソースに書いてあります。MetalContext.swift のコメントにこうあります3

This keeps the dev loop fast — edit a shader, rebuild the Swift target, no Xcode metallib step.

実装は device.makeLibrary(source:) で、Metal シェーダを起動時にランタイムコンパイルしています。ビルド時に metal コンパイラを呼ばないので、Command Line Tools で足りるわけです。

ただしこれは私の環境・CLT 26.6.0 での1例です。「Xcode は不要である」と一般化はできません。

--overwrite --resume を同時に付けると初回は失敗します

README にはこの2つを併記した例がありますが、初回インストールで両方付けるとこうなります。

install failed: no resumable install state exists for .../gemma4.gturbo.resume.json

--resume は中断状態のファイルが既にあることが前提です。初回は --overwrite だけが正解でした。

--prompt で素に叩くと出力が壊れます

 much like thethought own exampleer.
thought
It appears your sentence was cut off or contains some typographical errors

instruction tuned モデルにチャットの枠なしで raw completion を投げたためです。--messages-file を使えば正常に動きます。動作確認のつもりで --prompt を叩いて「壊れてる」と判断してしまうと、もったいないことになります。

スワップが増えなかった(記録した4時点では)

前回いちばん痛かったのがスワップでした。物理RAMを超えた瞬間に何もかもが遅くなり、ファンが回り続ける状態です。

今回、記録できた4ランではスワップの増加が観測されませんでした。前回と同じ機械で、モデルは前回より大きいのにです。

理由は方式そのものにあります。 スワップは「書き込み」を伴います。 書き込みは NAND を摩耗させますし、macOS では物理RAMを超えた瞬間に破滅的な連鎖に入ることがあります。llama.cpp の Issue には、M4 Pro / 24GB / macOS 26.2 の環境でこれが「swap death spiral」と表現され、カーネルパニックに至ったという報告があります4。ただしこれは一人のユーザーによる報告で、メンテナからの応答はなく、報告者自身がクローズしています。llama.cpp が公式に認めた挙動ではない点は補足しておきます。

対して expert streaming は 読み出しだけ です。心配の対象が「書き込みによる破滅」から「読み出しの詰まり」へ移った、というのが今回の本質でした。

SSD の寿命を心配する方もいると思いますが、読み出しは NAND を実質的に摩耗させません。ただしエネルギーは別問題です。SSD オフロードが トークンあたりのエネルギー(J/token)を最大約12倍に増やす という研究があります5。ここで注意が必要で、この研究のベースラインは HBM であり、対象は DeepSeek-R1 級のデータセンター構成です。Mac には HBM がないので比較対象そのものが存在しません。「読み出しはタダではない」という方向性の傍証として読むのが妥当です。

同じ入力で16回測ったら、2.8倍ブレた

ここからが本題です。

計測は公開されているコミュニティのベンチマーク手順に乗せました6。固定シード、固定プロンプト、temperature 0.2 / top-k 64 / top-p 0.95、コンテキスト4096、生成上限1024、warmup 1回は破棄、という手順です。

集計結果です。

項目
n16
最小4.298 tok/s
最大11.884 tok/s
中央値9.495 tok/s
最速÷最遅2.77倍

時系列で並べると、こうなります。

時刻tok/s備考
12:1610.124インストール直後
12:3711.884
12:409.921
12:454.298
12:506.666
12:525.281
15分アイドル
13:1110.630計装あり
13:1211.191計装あり
3時間10分の空白
16:23〜16:319.156 / 9.400 / 9.522 / 8.729 / 8.864 / 9.468 / 9.831 / 9.826powermetrics 併走

なお最後の8ランはフッタだけを記録しており、シードやフラグの記録が残っていません。prefill 61トークン・生成511トークン・stop=endOfTurn が一致していることからの推定です。

空きRAM量では説明できませんでした

まず簡単に潰せるものから潰しました。スワップは記録した範囲で増えていません。取り残しのプロセスも pgrep で0件でした。

空きRAMはどうか。ここが面白いところで、 単調な関係がありません

  • 空き 69,872ページ(約1.1GB)のとき → 10.630 tok/s(速い側)
  • 空き 219,595ページ(約3.4GB)のとき → 11.191 tok/s(速い側)
  • 空き 190,306〜206,070ページで推移していたブロックに → 4.298 tok/s が含まれる

空きが多ければ速い、少なければ遅い、という関係になっていません。

ここで最初、私は「ページキャッシュ説は否定できた」と考えました。 これは間違いでした。 macOS では ファイルキャッシュに使われているページは Pages free に計上されません。つまり「空きページが最低」は「キャッシュに余裕がない」ではなく、むしろ キャッシュが充填されている と読むほうが自然です。私の観測はページキャッシュ説を否定するどころか、整合してしまいます。

file-backed なページ数を直接測っていない以上、言えるのは「空きRAM量では説明できない」までです。実際、作者を名乗るユーザーも yeah, looks like a page cache matters a lot と述べ、メモリ圧をかけると 35 → 27 tok/s に落ちたと報告しています7。ページキャッシュは依然として有力な容疑者のままです。

「冷やせば戻る」とも言い切れませんでした

15分アイドルした直後の2ランは 10.630 / 11.191 と速い側に戻りました。ここだけ見ると「連続で回すと落ちて、休ませると回復する」という気持ちのいい物語になります。

ところが、 3時間10分空けてから走らせた8ランは 9.156〜9.831 で、10 tok/s に一度も届いていません。いちばん長く冷やした後のブロックが、15分アイドル後より低い側に出ています。

さらにこの8ランは sudo powermetrics を併走させた状態で走らせており、そのオーバーヘッドを分離していません。

したがって「アイドルで回復する」は、1回観測されたものの、その後の長時間アイドル後には再現していません。回復すると書くことはできませんでした。

原因は特定できていません

インストール直後には、別のプロンプトで 0.654 tok/s という極端な値も出ています(同じプロンプトの別の測定は 4.391 tok/s でした)。14.3GB を書き込んだ直後という時間帯のI/O競合が最有力ですが、特定はできていません。

計測上の抜けもあります。低速フェーズは計装を外してフッタだけを取っていたため、 TTFT と RSS の記録がありません。そのため「decode だけが落ちたのか、TTFT も伸びたのか」を低速時について言えません。言えるのは、速い時の TTFT が 10.2〜10.7 秒で安定していたことだけです。

単発測定は間違っていない。ただ、足りない

同じ構成での先行報告があります。Model Identifier まで一致する MacBookPro18,3 / M1 Pro / 8コア(6P+2E)/ 16GB で、同じプロンプトのケースで 9.477 tok/s という報告です8

私の16ランのレンジ(4.298〜11.884)は、この値を挟んでいます。

ここで「中央値がほぼ一致したので先行報告は正確だった」と書きたくなるのですが、それは踏み込みすぎです。16本のうち8本は連続実行の1ブロックで、中央値はそのブロックがほぼ決めています(前半8本だけの中央値は 10.02 です)。中央値の一致は独立した16サンプルの帰結ではなく、測定ブロックの構成に依存しています。

そのうえで言えることは、 先行報告が外れ値を引いたと責める理由はない、ということです。足りなかったのは分散の情報でした。

コミュニティのベンチマーク手順が求めているのは「各ケース warmup 1回(破棄)+ 測定1回」です6。同じ文書には Prompt length, generated tokens, storage, cache state, and the number of runs all affect decode speed. と実行回数の影響への言及があり、結果表12行のうち6行は「Median of five runs」で提出されています。分散の報告を禁じてはいませんが、必須にもしていません。

実務で使っている Mac では、 一つの数字より「レンジ」のほうが実用的な情報になる、というのが今回いちばん強く感じたことです。

熱を測りに行って、届かなかった話

ブレの原因が熱なのかを確かめようと、sudo powermetrics --samplers cpu_power,gpu_power,thermal を別ターミナルで走らせながら推論を回しました。

先に期間を正確に書いておきます。 powermetrics の観測窓は 16:22:12〜16:28:18 の6分06秒(74サンプル)負荷の実行は 16:22:34〜16:31:18 の8分44秒 です。末尾の約2分半は熱の計装がありません。

熱圧(thermal pressure level)は観測窓の 74サンプルすべてが Nominal で、他の値は一度も出ませんでした。

GPU の P-state 別の平均滞在率は、立ち上がりを除いた負荷区間69サンプルでこうなりました。

P-state平均滞在率
389 MHz24.61%
486 MHz14.07%
648 MHz20.10%
778 MHz18.74%
972 MHz7.05%
1296 MHz2.17%

負荷区間の69サンプルすべてで、最上段の 1296 MHz に到達していました。 つまり GPU は全開まで届いています。滞在が 2.17% と極端に短いだけです。

ここは私が最初に読み違えたところで、GPU HW active frequency として報告される値(負荷区間の中央値 614.0 MHz)を見て「クロックが上がらない」と解釈していました。実際にはこの値は6段に分散した滞在の 時間加重平均 であって、上限ではありません。

なお「1296 MHz」は自機の powermetrics に現れた P-state 階梯の最上段であって、M1 Pro 一般の公称最大クロックとして裏を取ったものではありません。

GPU の稼働率(active residency)は負荷区間で平均 86.75%、中央値 96.10% でした。 稼働率はほぼ埋まっているのに、最上段クロックの滞在は 2.17%。 一瞬だけ全開になってすぐ落ちる、という細切れの動き方です。

これは作者を名乗るユーザーの説明と整合します9

there is no constant load on ssd or gpu. i/o and gpu work are alternating and there is a brief idle periods for each i/o and gpu during inference (because gpu waits for i/o and after that i/o waits for gpu)

ただし、 これは「SSD が律速である」ことの直接的な証拠にはなりません。今回回したサンプラーは cpu_power,gpu_power,thermal だけで、ディスクI/Oの読み出しレイテンシもIOPSも帯域も一切測っていないからです。

で、熱はどうだったのか

前半と後半で比べると、負荷区間69サンプルの分割で 631.7 → 605.9 MHz、約4%の低下です。

最初これを「負荷開始直後のランプアップで説明できる」と考えましたが、ログを取り直すと 冒頭を除外しても減少が消えません

除外n前半→後半変化
なし69631.7 → 605.9 MHz−4.1%
冒頭663630.5 → 606.3 MHz−3.8%
冒頭1257625.5 → 604.1 MHz−3.4%
冒頭2049626.0 → 603.6 MHz−3.6%

素直に記述すると、負荷区間の最初の9サンプルが 644.9 MHz と高く、以降の60サンプルは 615.3 MHz でほぼ一定、という形です。

同じ窓の tok/s も同じ方向に動いています。共観測できた5点は前3点の平均 9.359 に対し後2点の平均 8.796 で、 −6.0% です。周波数の −4.1% よりむしろ大きい。片方を「微減」、片方を「横ばい」と呼び分けるわけにはいきません。

まとめると、こうなります。熱圧は観測窓を通じて Nominal のまま。GPU 周波数は −4.1%、tok/s は −6.0% で、 どちらも6分間で数%下向きにドリフトしていて向きは一致する。ただし5点では有意差として扱えませんし、6分06秒ではランプアップ・サーマル・負荷変動を切り分けられません。

つまりこの計測は、サーマル要因の否定にも肯定にも使えませんでした。 6分06秒では、そもそも低速側(4〜6 tok/s)が出た時間帯に到達していません。

作者を名乗るユーザーは発熱について I think it will throttle quite soon, but I haven't tried runs longer than 30minutes with this engine. と述べています9。この予想の当否も、6分の観測では判定できませんでした。

I/O 側は測れていません

「9 tok/s のとき毎トークンどれだけ読んでいるのか」は、この記事でいちばん答えたかった問いですが、答えられませんでした。ディスクI/Oを計測していないからです。

参考になるのは、作者を名乗るユーザーが公開している時間の配分です。expert の再利用が効いていて、 M2 Mac で I/O が 166ms/token から 88ms/token に下がる とされています。約40%の expert が次のトークンでも再利用され、2トークン以内なら57%だそうです10。16スロットのキャッシュでヒット率は約67%とのことです11

バイト数や帯域に換算したくなりますが、 1トークンあたりに読む expert の個数が公開されていない ため、掛け合わせても意味のある数字になりません。ここは時間の配分だけを紹介するに留めます。

「低スペックでも使える」は、どの軸で言うかで結論が変わる

この技術のいちばんの価値は「低スペックのマシンでも使えるようになること」だと考えていました。実測してみると、軸によって答えが変わります。

RAM の軸なら、はっきり YES です。 MLX なら 14GB、この方式なら 2GB 級です2。自機の実測でも peak RSS は 1.5GB 台でした。 MLX が要求する 14GB を払わずに済む (差分としては約12GB)というのは、16GB 機にとって決定的な差です。

ストレージの軸は、当初の見立てが調査で覆りました。

最初は「安いマシンほど SSD が小容量で遅いから、恩恵を受けてほしい層ほど不利になる」と考えていました。expert を毎トークン読む方式なら、SSD が律速になるはずだからです。

ところがこれは世代依存でした。確かに M2 世代の Mac mini では 256GB 構成が単一チップ化され、読み書きとも約1,500 MB/s、上位容量比で30〜50%遅いと報告されています(M1 では 128GB チップ2枚でした)12。しかし M4 で Apple は 128GB チップ2枚構成に戻しています 13

Apple has returned to using two 128GB storage chips in the new Mac mini with 256GB of storage.

This means the base-model Mac mini with the M4 chip will not have significantly slower SSD speeds compared to higher-end configurations.

そして、この記事で参照しているコミュニティの報告機はまさに M4 Mac mini です。つまり適用される世代は M4 のほうで、「安いマシンほど不利」は成立しません。

コミュニティのデータも一枚岩ではありません。 M4 mini 16GB / 256GB のユーザーが「just over 5 tok/s」と報告している一方14、同じ M4 mini 16GB / 256GB の内蔵SSDで 8.43〜11.38 tok/s(5回の中央値) という報告もあります15。食い違いの原因は分かっていません。どちらか一方を採用する根拠が私にはないので、両方を並べておきます。

なお誤読しやすい数字があります。後者の報告にある「+27.99% / +30.56%」は 外付けの Samsung 990 PRO と内蔵256GBの比較 であって、256GB と 512GB の内蔵同士の比較ではありません15

もう一つ、SSD 速度のレビューはたいていシーケンシャル帯域の計測です。この方式は細かな読み出しの繰り返しでレイテンシが効くので、そのまま転用はできません。

そして私の検証機の内蔵SSDは 494GB(512GBクラス) です。有利な側の構成なので、この実測を「低スペック機でも動く」の根拠に一般化するわけにはいきません。

結論としては、 RAM の軸なら裾野は確実に広がります。価格の軸で言えるかは、今回の検証では判定できませんでした。

結論:読ませる用途には届く。働かせる用途にはまだ

寄せられる側 は、読む用途です。チャット、要約、翻訳。人間の読速の目安として「7〜8 tok/s」を挙げている記事があります16。私の実測は中央値 9.495 なので、良い時は十分に上回ります。ただし 悪い時の 4.298 では読速を下回ります

寄せられない側 は、働かせる用途です。理由が4つあります。

1つ目。 アプリと CLI はツールを実行しません。 README にこうあります1

The app and CLI support user and model messages plus optional system guidance; they do not expose or execute tools.

ただし「ツール実行が非対応」と単純化するのは正しくありません。ループバックサーバは OpenAI 形式の tool call を返しますし(実行はクライアント側というのは OpenAI 互換 API では標準の挙動です)、公式ドキュメントには OpenCode の provider 設定が掲載されています17

2つ目。 Anthropic 形式には対応していません。 Claude Code 連携は機能要望として起票されていますが、応答が付いていない状態です18

3つ目。 TTFT です。 prefill 61トークンで約10秒(実測 10.2〜10.7秒)、prefill 430トークンでは 19.444秒まで伸びます。エージェント用途はプロンプトが長くなりがちなので、ここは効きます。

4つ目。 持続性能が読めません。 これがこの記事の主題そのものです。エージェント用途は往復が多いので、1回あたりのブレが積み上がります。

なお、エンジン側のコンテキスト上限は最大64K・既定16Kです1。モデル公称の 256K は使えません。

16GB 機には「MLX を使う」という逃げ道がない

作者を名乗るユーザーは「最大速度と柔軟性が欲しいなら MLX を使え」と書いています2。もっともな話です。 ただしそれは M5 Mac Pro での話です。

MLX は同じモデルに 14GB の RAM を要求します2。16GB の機体でそれをやれば、前回観測したスワップの状況に近づくと考えられます(MLX 自体は今回検証していないので、あくまで推論です)。

だからこの交換レートに意味があります。 「速度を落として RAM を返す」という取引は、RAM に余裕がある機体では割に合いません。 RAM が足りない機体でこそ、初めて選択肢になります。

自分のマシンで判定するには

今回の検証を踏まえて、判断材料の集め方を提案します。

1回測って終わりにしないでください。 この記事の16ランは 2.77倍ブレました。1つの数字は、その日その瞬間のスナップショットでしかありません。

使い込んだ状態でも測ってください。 ただし正直に書くと、今回は「冷やせば戻る」も確認できませんでした。ですので「何分休ませればよい」という基準を示すことができません。

公開されているベンチマーク手順に乗せると比較可能になります 6。固定シード・固定プロンプト・warmup 破棄が定められているので、他の人の報告と直接比べられます。私もこの手順に乗せたおかげで、同じ機種構成の先行報告と突き合わせることができました。

そして正直なところ、SSD 構成の軸も連続稼働時間の軸も、 今回の調査範囲では判定基準を作れませんでした。現時点での答えは「自分の機体で、複数回、時間を空けて測るしかない」です。

前回からの変化を一文にまとめます。 スワップという破滅的な失敗は(記録した範囲では)観測されなくなりました。代わりに、予測しづらい変動を受け取りました。 前者は使えない、後者は条件付きで使える。その差は小さくないと思います。


ローカルとクラウドの使い分けや、AI導入時のコスト設計について相談したいことがあれば、コンタクトフォーム からお気軽にどうぞ。

Footnotes

  1. drumih/turbo-fieldfare 2 3 4

  2. Show HN スレッドの投稿(MLX との比較) 2 3 4

  3. MetalContext.swift

  4. Managed SSD offloading for MoE to prevent macOS kernel panics(llama.cpp)

  5. SSD Offloading for LLM Mixture-of-Experts Weights Considered Harmful in Energy Efficiency

  6. COMMUNITY_BENCHMARKS.md 2 3

  7. Show HN スレッドの投稿(ページキャッシュの影響)

  8. Benchmark: M1 Pro, 16 GB, macOS 26.5.2

  9. Show HN スレッドの投稿(I/O と GPU の交互動作) 2

  10. Show HN スレッドの投稿(expert の再利用率)

  11. Show HN スレッドの投稿(キャッシュヒット率)

  12. New 256GB Mac Mini and 512GB MacBook Pro Have Slower SSD Speeds Than Previous Models

  13. New Mac Mini Has Modular Storage, 256GB Model Will Have Faster SSD

  14. Show HN スレッドの投稿(M4 mini での報告)

  15. Benchmark: Apple M4, 16 GB RAM / 256 GB SSD, macOS 26.3.1 — internal vs external SSD 2

  16. 24/365稼働の自分用ローカルLLMに最適なMacは? — 帯域でなくprefill(GPUコア)で選ぶ

  17. docs/OPENAI_SERVER.md

  18. Claude code integration