Mac(M4/32GB)でGemma 4をローカルで動かす。Docker Model Runnerで苦戦して、LM Studioに落ち着いた話

最近、ローカルLLMをちゃんと使ってみたくなりました。手元のMacBook Air(M4 / 32GB)でやってみた記録です。

やりたかったことは3つ。できればDockerでクリーンに。MacなのでMLXで最適化されたやつ。あとGemma 4みたいな新しめのモデルを動かして、ついでに評価もしたい。欲張りですね。

結論から先に書くと、普段のチャットはLM Studioが一番ラクでした。ただ、そこにたどり着くまでにDocker Model Runnerでけっこう苦戦したので、その過程も残しておきます。

まずDocker Model Runnerを試した

Macでローカルモデルを動かすとき、最初に引っかかるのがここです。MLXはApple SiliconのGPU(Metal)を直接使う仕組みなんですが、Dockerコンテナの中(実体はLinuxのVM)からはMacのGPUが見えません。なので素朴にコンテナ内でOllamaを動かすと、CPUだけで動いて激遅になります。

ここで便利なのがDocker Model Runnerでした。docker model というコマンドで使うんですが、Macの場合は推論エンジン本体をコンテナの外(ホスト)で動かして、Metalに直接アクセスしてくれます。しかもMLX形式のモデルはvllm-metalというバックエンドにちゃんと載る。「Dockerの使い勝手のままMLX加速」が一応成立します。

ただ、32GBという容量がここで効いてきました。

MLX版のGemma 4って、Docker Model Runnerのカタログ(docker model で引ける範囲)だと大きいやつ(26B-A4Bと31B)しか無いんですよね。小さい版はGGUFしか並んでいない。で、26B-A4Bの4bitでだいたい15.6GB。これを動かそうとすると、最初めちゃくちゃ遅くて、3トークン/秒くらいしか出ませんでした。

遅さの正体は「ページング」だった

なんでこんなに遅いのか調べたら、原因は計算の重さじゃなくてメモリのページングでした。

ページングというのは、物理メモリに載りきらない分をOSがSSDに退避して、必要なときに読み戻す仕組みのことです。RAMは100GB/s以上出るのに、SSDは数GB/sしかない。LLMは1トークン作るたびにモデルの重み全部を読むので、重みがSSDに溢れると毎回SSD待ちが入って、一気に遅くなります。

Macはユニファイドメモリといって、CPUとGPUで同じメモリ(32GB)を共有しています。でもGPUが固定して使える上限はだいたい全体の7割くらい(21GBくらい)。gpu-memory-utilization を0.85にすると27GBくらい要求してしまって、上限を超えてページングしていた、というオチでした。

これを0.6(19GBくらい)に下げたら、ページングが止まって8.5トークン/秒まで回復。さらに、素の4bitだと数式やコードの出力が崩壊するんですが、QAT版(量子化を前提に学習させたやつ)に変えたら25トークン/秒まで上がって、品質もまともになりました。同じモデル、同じMacなのに、設定だけで3倍くらい変わるんですね。

lmstudio-gemma4-05-docker-bench

ここまでで、Docker Model RunnerでもチューニングすればGemma 4は一応動く、というところまでは来ました。ただ、ひとつ厄介な癖が残っていて。

数を答えさせると、たまに「答え:1000000000…」みたいにゼロが暴走するんです。計算自体は合っているのに、最後にゼロを延々と吐き続ける。frequency_penalty を入れると止まるんですが、ちょっと気持ち悪い。

結局LM Studioが一番ラクだった

普段使いでこの調整を毎回やるのはしんどいので、ネイティブのMLXで動くLM Studioを試してみました。これが正解でした。

インストールは公式サイトからDMGを落としてApplicationsに入れるだけ。起動するとこんな画面が出ます。Get Startedを押します。

lmstudio-gemma4-01-welcome

ここで地味に感動したんですが、最初のモデルとしてGemma 4を勧めてくるんですよね。しかも gemma-4-e4b という効率版で、サイズは6.86GB。実体は gemma-4-E4B-it-MLX-4bit で、ちゃんとMLX版でした。

さっきDocker側で「MLXのGemma 4は大きいのしか無い」と書きましたが、あれは正確には「Docker Model Runnerのカタログに大きいMLX版しか並んでいなかった」というだけの話でした。E4Bの小さいMLX版自体はちゃんと存在していて、Hugging Face上にMLX版として公開されています。LM Studio専用なわけではなく、LM Studioがそれを標準で引っぱってきてくれただけ。要は、僕がDocker Model Runnerのカタログだけを見て「MLXは大きいのしか無い」と早合点していた、というオチでした。32GBで快適に動かすにはこのE4Bで十分です。Downloadを押します。

lmstudio-gemma4-02-recommend

ダウンロードは裏で進めながら次に進めます。途中のAdvanced settingsでDeveloper Modeをオンにしておくと、あとでOpenAI互換のサーバーも使えるようになります。

lmstudio-gemma4-03-download

ダウンロードが終わったらモデルをロードして、下の入力欄から話しかけるだけ。試しに「あなたは何というモデル?」と「りんご3個とみかん5個で合計何個?」をまとめて聞いてみました。

lmstudio-gemma4-04-chat

ここでひとつ知っておくといいのが、Gemma 4は考えてから答えるタイプ(thinkingモデル)だということです。先に「Thought for 22.71 seconds」みたいに思考の過程を表示して、そのあとに本文を返します。なので最初は黙っているように見えますが、ちゃんと考えています。

肝心の答えは「果物は合計8個です」。ちゃんと合ってます。さっきDocker側で出ていたゼロの暴走も、こっちでは一切出ませんでした。速度も17.7トークン/秒くらい出ていて、普段のチャットには十分です。

つまり、あの数の暴走はvllm-metal(Docker側)固有の問題で、ネイティブのMLXエンジンなら起きない、ということがここで分かりました。原因の切り分けまでできて、ちょっとスッキリしました。

使い分けに落ち着いた

最終的に、こういう使い分けに落ち着きました。

  • 普段のチャットはLM Studio。ネイティブMLXで速いし、数の答えも安定している。Gemma 4 E4Bが軽くてちょうどいい。
  • APIとして使ったり、ちゃんと評価したいときはDocker Model Runner。OpenAI互換のエンドポイントが綺麗に出るので、評価スクリプトを回すのに向いている。

ちなみにLM StudioもOpenAI互換のサーバーを localhost:1234 で立てられるので、他のアプリや自分の評価スクリプトからも叩けます。どちらもOpenAI互換なので、向き先のURLを差し替えるだけで同じ土俵で比べられるのも地味に便利でした。

要するに、Macで新しめのモデルをローカルで動かしたいなら、まずLM Studioを入れてGemma 4 E4Bを試すのが手っ取り早い、という話でした。Dockerで頑張るのは、もう少し作り込みたくなってからでいい気がします。