前回の記事 で、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 release は 146.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 のユーザーが「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 ↩