llmfit は、ハードを検出して「このマシンでどのローカルLLMが動くか」「何 tok/s 出るか」を推定してくれるツールです。★3.5万・MIT。その推定は当たるのかを、M4 MacBook Air 16GB で実測と突き合わせました。
合格ラインは測る前に決めました。**推定と実測の差が ±20% 以内なら「当たり」**とします。
| 問い | 答え | 根拠 |
|---|---|---|
| 速度(tok/s)を推定できたか | できた。ただし構成を指定したときだけ | plan に量子化とコンテキストを渡した推定は、実測の 85〜115%。5回の突き合わせのうち4回が±20%内 |
| 何も指定しない既定の値は当たるか | 当たらない | 一覧(fit)の値は実測の 55〜75%。原因は精度ではなく前提(mlx-8bit で計算している) |
| メモリ使用量を推定できたか | だいたい当たった | 推定÷実測が 106%・122%・128%。3件とも多めに見積もる安全側 |
| 普段使いでメモリが埋まった日も当たるか | 当たらない | 同じMac・同じモデルの実測が 5.7〜36.3 tok/s(6倍差)。推定は1つの値のまま |
まとめると、買う前・入れる前に当たりを付ける道具としては使えます。ただし量子化とコンテキストを自分で指定して読むこと。速度の絶対値は「メモリに余裕がある日の値」として受け取るのが正しいです。
測った環境
| 項目 | 値 |
|---|---|
| マシン | MacBook Air(Apple M4・10コア・16GB ユニファイドメモリ)・macOS 26.5 |
| llmfit | 1.1.15(GitHub Releases の aarch64-apple-darwin バイナリ・sha256 検証済み)。比較用に 1.1.14 も |
| Ollama | 0.33.3(公式の ollama-darwin.tgz) |
| MLX | mlx-lm 0.31.3 / mlx 0.32.2(arm64 の Python 3.12) |
| モデル | gemma3:4b(Q4_K_M・4.3B・ディスク3.11GiB)、qwen3.5:4b(Q4_K_M・4.7B・3.16GiB)、mlx-community/gemma-3-4b-it-4bit |
| 状態 | 普段どおりアプリを開いたまま(Slack・Chrome・Notion・エディタ等)。空きメモリとスワップは1回ごとに記録 |
計測はあえて普段使いのままにしています。 「このMacで動くのか」を知りたいのは普段の状態で使うからで、アプリを閉じて空きメモリを作った数字は自分の役に立ちません。代わりに、そのときの空きメモリ%とスワップ使用量を一緒に載せます。
測り方
llmfit 側は2通りの推定を取りました。同じツールでも出てくる数字が違います。
# ① 何も指定しない既定の一覧(8,410モデルを並べる)
llmfit fit --json --no-dashboard
# ② 実際に使っている構成を指定する(量子化とコンテキスト長)
llmfit plan "google/gemma-3-4b-it" --context 4096 --quant Q4_K_M --json実測は2つのランタイムで揃えました。プロンプトは同じ日本語の指示文、最大200トークン、greedy(温度0)、seed=42、5回まわして中央値です。
- Ollama:
/api/generateにnum_ctx=4096で投げ、eval_count ÷ eval_duration(生成部分だけ)を tok/s とする。モデル間はkeep_alive:0でアンロード - MLX: mlx-lm の
stream_generateで、チャットテンプレートを通した同じプロンプトを流し、generation_tpsとpeak_memoryを採る
9月6日の計測だけ3回の中央値です。それ以外は5回です。
結果1: 構成を指定した推定は当たった

| 実測した構成 | ②構成を指定した推定 | 実測(中央値) | 推定÷実測 | 判定 |
|---|---|---|---|---|
gemma3:4b Ollama Q4(9月6日) | 30.7 tok/s | 31.3 tok/s | 98% | 当たり |
gemma3:4b Ollama Q4(9月12日・MLX の前) | 30.7 tok/s | 26.8 tok/s | 115% | 当たり |
gemma3:4b Ollama Q4(9月12日・MLX の後) | 30.7 tok/s | 27.8 tok/s | 110% | 当たり |
gemma3:4b MLX 4bit(9月12日) | 30.7 tok/s | 36.3 tok/s | 85% | 当たり |
qwen3.5:4b Ollama Q4(9月6日) | 28.3 tok/s | 24.7 tok/s | 115% | 当たり |
ランタイムを Ollama から MLX に変えても帯の中に収まりました。llmfit は Q4_K_M と mlx-4bit に同じ 30.7 tok/s を返すので、4bit なら実装の違いは推定の誤差の内側、という結果です。
plan はメモリ量も返します。こちらも近い値でした。
| 実測した構成 | 推定メモリ(KVキャッシュ込み) | 実測 | 推定÷実測 |
|---|---|---|---|
gemma3:4b Ollama Q4 | 3.53GB(うちKV 0.53GB) | 2.89GB(ollama ps の占有) | 122% |
qwen3.5:4b Ollama Q4 | 3.33GB(うちKV 0.13GB) | 3.15GB(同) | 106% |
gemma3:4b MLX 4bit | 3.40GB | 2.65GB(mlx のピーク) | 128% |
3件とも多めに見積もる方向です。「載るか」の判断に使うなら、この向きの誤差は安全側です。
結果2: 既定の一覧が外れるのは前提の違い
一覧(fit)の値は gemma3:4b が 20.1、qwen3.5:4b が 18.5 でした。実測の 55〜75% で、帯の外です。ただし中身を見ると、外れているのは式ではなく前提です。
llmfit は「メモリに余裕があるなら、いちばん品質の高い量子化で動かすべき」という設計で、この Mac には mlx-8bit を選びます。私が入れているのは Q4_K_M と mlx-4bit。別の構成の数字を見ていたわけです。同じ plan で量子化を変えるとこうなります。
| 指定した量子化 | gemma3:4b | qwen3.5:4b |
|---|---|---|
Q4_K_M(Ollama の既定タグ) | 30.7 | 28.3 |
mlx-4bit | 30.7 | 28.3 |
mlx-8bit(llmfit が勧める構成) | 15.3 | 14.2 |
Ollama を起動していても変わりません。installed: true と検出はされるのに、best_quant は mlx-8bit のままです。「あなたが入れているモデルの速度」ではなく「このモデルをこのハードで動かすなら、こう動かすべき」の推定だと読むのが正しいです。
もう1つ、同じモデルでコマンドによって数字が変わる件も残っています。fit は 20.1、plan --quant mlx-8bit は 15.3。比は 1.31 で、これは fit が掛けているローカル較正係数(起動時に測った実効帯域から出る 1.309)と一致します。Web ダッシュボードは較正なしの側で、CLI の fit が 11.7 と出すモデルが画面では 8.9 と出ます。比べるときは1つのコマンドに揃えてください。

結果3: 同じモデルが、日によって6倍振れる
普段使いのまま、同じコマンドで測った gemma3:4b の実測を並べます。

| 計測 | 空きメモリ | スワップ使用量 | 実測(中央値) | ばらつき |
|---|---|---|---|---|
| 9月12日 MLX 4bit | 31〜35% | 約15GB | 36.3 tok/s | 31.9〜37.5 |
| 9月6日 Ollama Q4 | llmfit 表示で 1.3〜2.4GB | 未記録 | 31.3 tok/s | 31.2〜33.7 |
| 9月12日 Ollama Q4 | 21〜44% | 13〜16GB | 26.8 tok/s | 22.1〜27.9 |
| 9月11日 Ollama Q4 | 19〜21% | 15→19GB | 5.7 tok/s | 3.8〜8.9 |
最も遅い日と最も速い計測で6倍の差です。9月11日もモデルは GPU に全部載っていて(ollama ps の VRAM 表示は 2.69GiB / 2.69GiB)、初回ロードに 16〜58秒かかり、5回の中でも倍以上ばらつきました。スワップが19GB積まれた状態では重みがメモリに居続けられず、読み直しが起きていると考えられます(ここは推測で、ページフォールト数までは測っていません)。
llmfit の推定は、この「その瞬間のメモリの混み具合」を速度に反映しません。plan の 30.7 tok/s は、スワップ13〜16GBの日には 110〜115% で当たり、19GBまで積んだ日には 5.4倍の過大評価になります。**推定値は「メモリに余裕がある日の値」**として読むのが実際に合います。
つまずいたところ
- Homebrew がソースビルドを始めた。 このMacの Homebrew は
/usr/localにある Intel 版で、macOS 26.5 用の bottle が無く、brew install llmfitもbrew install ollamaも Go と CMake のビルドに入りました。10分以上終わらないので打ち切り、GitHub Releases のバイナリに切り替えています。Apple Silicon 版(/opt/homebrew)の人には起きない話です - PyPI 版を入れると x86_64 バイナリが降ってくることがあります。 原因は PyPI ではなく、このMacの pyenv の Python 自体が x86_64 だったことです(
python3 -c "import platform;print(platform.machine())"で分かります)。今回 arm64 のネイティブバイナリでも測り直しましたが、推定値も較正係数も完全に同じでした(20.1 / 18.5 / 1.309)。Rosetta 経由かどうかは推定に影響しません - 前回の
benchの結果が残って、一覧の数字を上書きしていました。~/Library/Application Support/llmfit/benchmarks/pending/に送信待ちのファイルが2件残り、estimate_confidenceがmeasured_localに変わって「この機体の実測」として表示されます。退避したらcalibratedに戻りました - 付属の
benchは Ollama 自身の計測と定義が違います。qwen3.5:4bで 13.9 tok/s(同じサーバーへの自前計測は 24.7)。プロンプト処理を含む所要時間で割っているように見えます。その値がそのままmeasured_localとして保存されるので、回す前にeval_count ÷ eval_durationと一度並べるほうが安全です - Ollama の占有が計測の回によって 2.69GiB と 3.47GiB に変わりました。 同じモデル・同じリクエストで、理由は追えていません。メモリ推定との比較には小さいほうの値を使っています
- ディスクが足りませんでした。 空きが 5.7GB しかなく、13GB ある
gpt-oss:20bの追加検証は諦めています
再現手順
# 1. llmfit(ネイティブ arm64 バイナリ。sha256 を検証する)
mkdir -p ~/sandbox/llmfit && cd ~/sandbox/llmfit
gh release download v1.1.15 -R AlexsJones/llmfit --pattern 'llmfit-v1.1.15-aarch64-apple-darwin.tar.gz*'
shasum -a 256 -c llmfit-v1.1.15-aarch64-apple-darwin.tar.gz.sha256
tar xzf llmfit-v1.1.15-aarch64-apple-darwin.tar.gz
L=$PWD/llmfit-v1.1.15-aarch64-apple-darwin/llmfit; $L --version
# 2. 既定の一覧と、構成を指定した推定の両方を取る(数字が違う)
$L fit --json --no-dashboard > fit.json
$L plan "google/gemma-3-4b-it" --context 4096 --quant Q4_K_M --json > plan.json
python3 -c "import json;d=json.load(open('plan.json'));p=[x for x in d['run_paths'] if x['path']=='gpu'][0];print(p['estimated_tps'], p['minimum']['vram_gb'])"
# 3. Ollama(公式バイナリ。モデルも作業ディレクトリに置く)
gh release download -R ollama/ollama --pattern 'ollama-darwin.tgz' && tar xzf ollama-darwin.tgz
OLLAMA_MODELS=$PWD/models ./ollama serve &
./ollama pull gemma3:4b
# 4. 実測(生成部分だけの tok/s。5回まわして中央値)
for i in 1 2 3 4 5; do
curl -s http://localhost:11434/api/generate -d '{"model":"gemma3:4b","prompt":"製造業の中小企業がローカルLLMを導入する利点と注意点を、日本語の箇条書きで5つ、簡潔に説明してください。","stream":false,"options":{"num_predict":200,"seed":42,"num_ctx":4096}}' > run-$i.json
done
jq -s 'map(.eval_count / (.eval_duration/1e9)) | sort | .[2]' run-*.json
# 5. そのときのメモリ状況も一緒に残す(これが無いと後から読めない)
memory_pressure | tail -1; sysctl -n vm.swapusage; ./ollama psMLX 側は arm64 の Python で uv pip install mlx-lm し、stream_generate の generation_tps を採りました(pyenv の Python が x86_64 だと mlx が入りません)。
測っていないこと
- mlx-8bit の実測。 llmfit が勧める構成そのものでは測っていません。今回の「当たった」は 4bit 系(Q4_K_M と mlx-4bit)を指定したときの話です
qwen3.5:4bの MLX 実測。 ディスクの都合で gemma だけです- 12B 以上のモデル。 「載るか」の判定が効くのは境界付近ですが、ディスクの空きが足りず 13GB 級を落とせませんでした。今回の3GB級はどちらも余裕のある側(Perfect 判定)です
- メモリが埋まると遅くなる仕組み。 スワップ19GBという状態と6倍の低下は測りましたが、ページフォールト数やディスク読み出し量までは見ていません
- CPU のみで動かしたとき。
planは CPU 経路の推定(7.2 / 6.7 tok/s)も返しますが、Apple Silicon では GPU で動くので比較していません

