OllamaがMLX対応したらしいのでApple MLXを調査して、M2 Maxで計測してみた

OllamaがMLX対応したらしいのでApple MLXを調査して、M2 Maxで計測してみた

OllamaがApple SiliconでMLXエンジンを採用した理由と、従来のllama.cpp(GGUF)との違いを調べ、M2 Maxで実際に速度を計測してみました。思想の違い・量子化方式・メモリ効率から、どちらを選ぶべきかを整理します。
2026.06.27

どうも!オペ部の西村祐二です!

ローカルでLLMを動かすのにOllamaを使っている方は多いと思います。そのOllamaが、Apple Silicon上で内部エンジンにAppleの機械学習フレームワークMLXを採用したというブログ記事を読みました。

なぜ、これまで使っていたllama.cppではなくMLXなのか気になりました。OllamaはローカルLLM界隈で広く使われている分、裏側で何が起きているのかは把握しておきたいところです。

そこで今回は、Apple MLXとは何なのか、なぜApple Siliconで高速に動作するのか、従来のGGUF(llama.cpp)と何が違うのかを調べました。

最後に、簡易的ではありますが手元のMacBook Pro(M2 Max / 32GB)で実際にGGUFとMLXを動かし、速度とメモリを計測しました。

この記事で見た範囲と見ていない範囲

  • 調査対象: Apple MLXフレームワーク本体(設計思想・エコシステム)と、OllamaにおけるMLXの採用
  • 確認した範囲: MLX公式リポジトリ/ドキュメント、Ollama公式ブログとリリースノート、GGUF(llama.cpp)との量子化・思想の違い、M2 Maxでのgemma4 12Bの実機計測
  • 確認しなかった範囲: MLXでのファインチューニングの実行(推論のみ計測)、Intel Macやx86環境、M3以降の新しいチップでの計測、mlx-lm単体やLM Studioなどとの横断比較

Ollamaは何を変えたのか

最初に、キッカケとなった「Ollamaの変更」を時系列で整理します。ここを正確に押さえておくと、後半のMLXの話がつながりやすくなります。

OllamaがApple SiliconでMLXをエンジンに採用したのは、v0.30ではなく、それより前のv0.19のプレビューです。Ollama公式ブログ「Ollama is now powered by MLX on Apple Silicon (in preview)」にはこう書かれています。

Ollama on Apple silicon is now built on top of Apple's machine learning framework, MLX, to take advantage of its unified memory architecture.

つまり「Apple Silicon上のOllamaは、統合メモリアーキテクチャを活かすためにMLXの上に作り直された」ということです。単なるオプション追加ではなく、推論エンジンの土台を載せ替えた、という位置づけになっています。

その後のv0.30.0(2026-05-13)のリリースノートは、MLXを新規追加したものではなく、MLXを補完する内容でした。

Ollama 0.30 is now available, with improved compatibility and performance using llama.cpp. This augments the MLX engine on Apple Silicon, bringing support to a wider range of hardware.

ここから読み取れるのは、Ollamaは「MLXへ完全移行してllama.cppを捨てた」わけではない、という点です。Apple SiliconではMLXエンジンを使いつつ、llama.cppも統合しています。これにより、より広いハードウェアやモデル(Hugging FaceのGGUF、自前のファインチューンモデル)にも対応する二本立ての構成になっています。

実際、v0.30系のポイントリリースを追うと、MLX安定化の修正が継続的に入っています。最新のv0.30.10時点でもMLX埋め込み層や対応モデル拡張などの改善が継続している状況です。

公式が主張しているMLX採用の利点は、主に次の3点です。

  • 速度: Apple Siliconでの推論速度向上。特にM5系チップでは新しいGPUのNeural Acceleratorsを使い、最初のトークンが出るまでの時間(TTFT)と生成速度(tokens/秒)の両方を向上させる
  • 量子化フォーマット: NVIDIA由来のNVFP4という4bit量子化(モデルを小さく圧縮する方式)を活用し、精度を保ちながらメモリ使用量とファイルサイズを削減する(詳細はOllamaの性能改善ブログを参照)
  • メモリ/キャッシュ: 会話をまたいでキャッシュを再利用し、メモリ使用量を抑える

ここで「プリフィル」と「デコード」という言葉が出てきます。プリフィルは入力文(プロンプト)を読み込む段階、デコードは答えを1トークン(単語の断片のような単位)ずつ生成していく段階です。以降の速度はこの2つに分けて見ていきます。

公式ブログのベンチマーク(M5、Qwen3.5-35B-A3B)では、デコードが58→112 tokens/秒、プリフィルが1,154→1,810 tokens/秒と報告されています。ただしこれは旧実装(0.18 / Q4_K_M)と新実装(0.19 / NVFP4)の比較であり、量子化形式が揃っていない点に注意が必要です。世代間で単純に2倍速くなった、という読み方はできません。後半の実機計測では、ここを揃えて確かめます。

Apple MLXとは何か

Ollamaが土台に選んだMLXとは、そもそも何なのかまとめていきます。

MLXは、Appleの機械学習研究チーム(ml-explore)が開発している、Apple Silicon向けの配列フレームワーク(数値計算の土台となるライブラリ)です。READMEの定義はシンプルです。

MLX is an array framework for machine learning on Apple silicon, brought to you by Apple machine learning research.

2023年12月に公開され、ライセンスはMITです。Python / C++ / C / Swift のAPIを備え、設計はNumPy・PyTorch・JAXの影響を強く受けています。位置づけとしては「推論専用ツール」ではなく、学習・ファインチューニング・研究までを見据えた汎用の機械学習フレームワークです。

設計上の特徴のうち、ローカルLLM運用の観点で押さえておきたいのは次の4つです。

統合メモリモデル(unified memory)

MLXがほかのフレームワークと最も違うのが、この統合メモリモデルです。

A notable difference from MLX and other frameworks is the unified memory model. Arrays in MLX live in shared memory.

GPUを別に積んだ一般的なPCでは、計算する前にデータ(モデルの重み)をGPU専用のメモリへコピーし、計算が終わったら書き戻す、というやり取りが必要です。一方、Apple SiliconはCPUとGPUが1つのメモリを共有しています。MLXのデータは最初からこの共有メモリ上にあるので、CPUとGPUの間でコピーして「移動」させる必要がありません。公式ドキュメントの表現を借りると、こうなります。

rather than moving arrays to devices, you specify the device when you run the operation.

「データをデバイスへ動かす」のではなく「処理ごとにどこ(CPUかGPUか)で実行するかを指定する」という発想です。LLMの推論はメモリ帯域がボトルネックになりやすいため、コピーを減らせるこの設計はLLM推論に適しています。

遅延評価(lazy evaluation)

Computations in MLX are lazy. Arrays are only materialized when needed.

MLXは遅延評価という方式をとります。計算する手順を書いても、その場ではすぐ実行せず、手順だけを覚えておきます。実際の計算は、結果が必要になったとき(eval() の呼び出しや、配列の表示・NumPy変換など)に初めてまとめて走ります。出力に使われない計算は省かれるため、無駄なメモリ消費を抑えられます。

合成可能な関数変換

MLX supports composable function transformations for automatic differentiation, automatic vectorization, and computation graph optimization.

自動微分や自動ベクトル化など、機械学習の学習に必要な計算機構を備え、それらを組み合わせて使えます(JAX譲りの設計)。推論だけなら直接意識する必要はないものの、学習まで回せる土台があるのは強みです。入力データの形が変わっても、作り直し(再コンパイル)で待たされにくい点も特徴になります。

NumPy/PyTorchライクなAPI

コアのAPIはNumPyに近く、上位の mlx.nnmlx.optimizers はPyTorchに近い設計のようです。既存のPyTorch経験者が移りやすいように作られています。

MLXのエコシステム

MLX本体は「配列フレームワーク」なので、LLMを動かす際はその上に乗るパッケージを使います。Ollamaもその利用者の1つ、という見方ができます。

パッケージ 役割
mlx-lm Apple Silicon上でLLMのテキスト生成と追加学習(ファインチューニング)を実行。LoRA/QLoRAなどの省メモリ学習方式に対応し、量子化やHugging Face Hubへのアップロードも可能
mlx-vlm Vision-Language Model(VLM)の推論・ファインチューニング(コミュニティ製)
MLX Swift Apple純正アプリに組み込むためのSwiftバインディング
Hugging Face mlx-community MLX形式の事前量子化モデル(4bit/8bitなど)の配布元。mlx-lm からそのまま読み込める

最近の動向として、Linux + NVIDIA GPU向けのCUDAバックエンドが追加されています(2025年からのプレビュー)。ただしこれは、Apple以外の環境でMLXのモデルを開発・移植できるようにするためのもので、NVIDIAエコシステムと性能で正面から競うものではない、という位置づけです。

また、M5世代ではGPUコアに統合されたNeural AcceleratorsがMLXのために設計されており、Apple自身がチップ設計とMLXを連携させて最適化していることが分かります。

GGUF(llama.cpp)との違い

MLXで気になるポイントの一つは、いま使っているGGUF(llama.cpp)と何が違うのか、だと思います。大きく2つの軸で整理します。

1. 思想の違い

llama.cppは推論に特化したエンジンで、Windows / macOS / Linux、CPU / 各種GPUと幅広く動く成熟したプロジェクトです。Ollama・LM Studio・Janなど多くのツールの土台になっています。一方のMLXは、自動微分や関数変換を備えた汎用の機械学習フレームワークで、Apple純正かつMetal GPU + 統合メモリに密結合しています。学習・ファインチューニングまでMac上で完結させることを狙っている点が、推論専用のllama.cppとの根本的な違いです。

2. 量子化方式の違い

そもそも量子化とは、モデルの重み(パラメータ)を少ないビット数で表して、ファイルサイズとメモリを小さくする圧縮のことです。同じ「4bit量子化」でも、その中身は方式によって異なります。GGUFのK-quant(Q4_K_Mなど)は、1つの層の中でも場所によってビット数を変えます。多くを4bitにしつつ、品質に効きやすい部分だけ6bitに上げる、といった作りです。これに対してMLXの量子化は、層の中では基本的に同じビット数で均等に圧縮します。一般に、同じビット数ならGGUFのK-quantのほうが品質面でわずかに有利、MLX側はファイルサイズがわずかに小さくなる傾向です。

ここに新しく加わったのがNVFP4です。これはNVIDIA由来の4bitの方式で、重みの値の幅を場所ごとにきめ細かく捉えることで、圧縮による品質の劣化を抑えることを狙っています。前述のとおり、Ollamaの公式ベンチで大きな高速化が出ていたのは、このNVFP4と組み合わせたケースでした。「MLXにしたから速い」のか「NVFP4だから速い」のかは分けて考える必要があります。

M2 Maxで確かめる

ここからは、手元のMacBook Pro(M2 Max / 32GB)で、同じgemma4 12BをGGUFとMLXで動かして比較します。公式ベンチはM5 + NVFP4寄りの条件だったので、世代の異なるM2 Maxで量子化以外の条件を揃え、どこまで効果が出るかを確かめます。

検証環境

  • MacBook Pro / Apple M2 Max / 32GB unified memory / macOS 15.7.4
  • Ollama 0.30.10
  • 計測モデル(どちらもgemma4の12Bクラス)
    • GGUF: gemma4:12b-it-q4_K_M(Q4_K_M量子化、llama.cppバックエンド)
    • MLX: gemma4:12b-mlx(NVFP4量子化、MLXエンジン)

計測の手順

まず比較用に3つのモデルを取得しました。GGUF版1つと、MLX系2つ(-mlx-nvfp4)です。

ollama pull gemma4:12b-it-q4_K_M   # GGUF版
ollama pull gemma4:12b-mlx         # MLX版
ollama pull gemma4:12b-nvfp4       # NVFP4版(比較用)

速度はOllamaのHTTP APIで測りました。/api/generatestream: false でリクエストを送ると、レスポンスに処理時間の内訳が入っています。ここから次の2つを計算しています(時間はナノ秒単位なので10億で割って秒に直します)。

  • プリフィル: prompt_eval_count(入力トークン数)÷ prompt_eval_duration(入力を読み込んだ時間)
  • デコード: eval_count(生成トークン数)÷ eval_duration(生成にかかった時間)

1回分のリクエストはこんな形です。gemma4 は思考(thinking)に対応したモデルなので、生成量を揃えるため think: false を付けています。

curl -s http://127.0.0.1:11434/api/generate -d '{
  "model": "gemma4:12b-mlx",
  "prompt": "<計測用のプロンプト>",
  "stream": false,
  "think": false,
  "options": { "num_predict": 300, "temperature": 0.0 }
}'

これを2種類のプロンプトで回しました。狙いは、プリフィル(入力読み込み)とデコード(生成)のどちらが速いかを分けて見ることです。

  • デコード重視: 短い質問を投げ、num_predict で約300トークンを生成させる
  • プリフィル重視: 約7,000トークンの長文を読ませ、生成は少しだけ(num_predict: 8)にする

計測で注意したのは2点です。1つ目は、各モデルとも本番前に1回ウォームアップを挟み、モデル読み込み時間を計測から除いたこと。2つ目は、プリフィルでは毎回プロンプト先頭にユニークな文字列を付け、3回測って中央値を採ったこと。同じ入力を続けて送るとプロンプトキャッシュが効いて読み込みがほぼ0秒になり、正しく測れないためです。デコード側は同条件で3回ずつ測定し、いずれも値が安定していたため中央値を採用しています。

繰り返しのリクエストはPythonスクリプトにまとめました。APIを叩いて時間の内訳から速度を計算する中心部分は、これだけです。

bench.py
import json, urllib.request

def generate(model, prompt, num_predict):
    body = {
        "model": model, "prompt": prompt, "stream": False, "think": False,
        "options": {"num_predict": num_predict, "temperature": 0.0},
    }
    req = urllib.request.Request(
        "http://127.0.0.1:11434/api/generate",
        data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=600) as r:
        d = json.load(r)
    prefill = d["prompt_eval_count"] / (d["prompt_eval_duration"] / 1e9)
    decode = d["eval_count"] / (d["eval_duration"] / 1e9)
    return prefill, decode

なお、実運用ではキャッシュヒット時に prompt_eval_duration0 を返してZeroDivisionErrorになるケースがあります。本記事ではプロンプト先頭にユニーク文字列を挿入してキャッシュを無効化しているので発生していませんが、汎用化する場合はゼロ除算ガード(prompt_eval_duration が0なら None を返すなど)を入れておくと安全です。

気づき1: -mlxタグの実体はNVFP4だった

モデルを取得して ollama list で並べたところ、gemma4:12b-mlxgemma4:12b-nvfp4 は同じID(同じ実体)でした。

gemma4:12b-mlx          197a75677efb    6.8 GB
gemma4:12b-nvfp4        197a75677efb    6.8 GB
gemma4:12b-it-q4_K_M    4eb23ef187e2    7.6 GB

さらに ollama show gemma4:12b-mlx で中身を見ると、量子化は nvfp4 でした。

  Model
    architecture        gemma4_unified
    parameters          12.0B
    quantization        nvfp4

つまりこのモデルでは「MLX版」と「NVFP4版」は中身が同一で、-mlxタグの実体はNVFP4量子化です。前半で触れた「MLXだから速いのか、NVFP4だから速いのか」という問いは、Ollamaの既定のMLXモデルを使う限り分離されていません。Ollamaが配布する -mlx モデルでは現状NVFP4が選ばれているためです(MLXエンジン自体は他の量子化形式も扱えるものの、Ollamaの公式タグでは現状NVFP4が標準)。

気づき2: メモリと既定コンテキスト

両モデルを動かした状態で ollama ps を見ると、MLX版のほうがメモリ使用量が小さく、既定のコンテキスト長が大きく取られていました。

NAME                    ID              SIZE      PROCESSOR    CONTEXT
gemma4:12b-mlx          197a75677efb    6.9 GB    100% GPU     262144
gemma4:12b-it-q4_K_M    4eb23ef187e2    8.1 GB    100% GPU     65536
項目 GGUF Q4_K_M MLX NVFP4
メモリ使用量(ollama ps 8.1 GB 6.8 GB
既定コンテキスト長 65,536 262,144

ただしGGUF版(gemma4:12b-it-q4_K_M)はvision(CLIP系のProjector)と音声を含むマルチモーダル構成のため(ollama showprojector: clip を確認)、その分メモリが乗っている点は差し引いて見る必要があります。それでも、NVFP4による圧縮でMLX版が軽量に収まっている点は、32GB機で大きめのモデルを動かす際にありがたい差です。

気づき3: デコードはほぼ同等、プリフィルはGGUFがやや速い

速度の計測結果です。Ollamaには一度読んだ入力を覚えておく仕組み(プロンプトキャッシュ)があり、同じ入力をもう一度送ると読み込みが省かれて正しく測れません。そこでプリフィルは、毎回先頭に違う文字列を付けてキャッシュを無効化し、約7,000トークンの入力で3回測った中央値を使いました。デコードは約300トークンを生成させて計測しています。

指標 GGUF Q4_K_M MLX NVFP4
プリフィル(約7,000トークン入力、中央値) 約156 tok/s(146〜159、安定) 約114 tok/s(112〜117、安定)
デコード(300トークン生成、中央値) 約13.7 tok/s(13.56〜13.75) 約14.0 tok/s(13.70〜14.10)

結果を順に見ていきます。

デコードはMLXがごくわずかに速い程度で、ほぼ同等。14.0 tok/s 対 13.7 tok/s で、差は2%強しかありませんでした。デコードでは1トークン生成ごとに多くの重みを繰り返し読み出すため、メモリ帯域の影響を強く受けます。NVFP4はq4_K_Mより1トークンあたりの読み出し量が小さい一方、GGUF版のq4_K_M(7.6GB)とMLX版のnvfp4(6.8GB)の差はそこまで大きくなく、今回のM2 Max環境ではデコード速度に大きな差として出ませんでした。Ollama公式の性能改善ブログでは「NVFP4はq4_K_Mより約20%速く生成する」と報告されていますが、これはM5系チップでの値であり、M2世代では差は小さくなる傾向のようです。

プリフィルはGGUFが約1.4倍速い。長文入力の処理ではGGUFが約156 tok/sで安定し、MLXは約114 tok/sでした。注目したいのは、MLXのプリフィルが3回とも112〜117 tok/sでまったく変動しなかったことです。MLXはJIT(実行時)コンパイルやMetalカーネルの初回準備のために初回が遅くなりやすい仕組みを持ちますが、今回の計測ではウォームアップで1回モデルをロード・実行してからの計測としているため、初回ペナルティが安定して切り離せた結果と見ています。

Ollama公式の性能改善ブログでは、MLXのJITコンパイラによるMetalカーネル融合(原文「several operations are now fused into single Metal kernels via MLX's just-in-time compiler features」)に触れられています。0.30系のポイントリリースでもMLX安定化の修正が継続的に入っており、Ollama 0.30.10時点ではMLXのプリフィルがGGUFと並ぶ水準まで近づいてきているのが、手元での印象です。

注意したいのは、UIやログで目立つ生成速度(tok/s)は、多くの場合 eval_count / eval_duration、つまりデコード速度を指します。長い入力を読ませる用途では、prompt_eval_duration(プリフィルの時間)も併せて見ないと、体感の遅さを見落としがちです。

なお、公式が示していた「デコードが約2倍」というベンチはM5チップでの旧Q4_K_M(0.18)対NVFP4(0.19)の比較でした。M2 Maxで量子化以外の条件を揃えて測ると、デコードの差はほぼゼロにとどまりました。公式の大きな数値には、M5世代のGPU Neural Acceleratorsの寄与が大きく含まれていると考えられます。MLXの恩恵はチップ世代に強く依存する、というのが手元での実感です。

OllamaでMLXを使うには

Apple Siliconであれば、MLX形式のモデル(タグに -mlx-nvfp4 が付いたもの)を指定して動かすだけです。特別なフラグや環境変数は要りません。

ollama run gemma4:12b-mlx

タグの付かない標準のモデルはGGUF(llama.cppバックエンド)で動きます。同じモデルにMLX版があるかどうかは、ollama.com のモデルページのタグ一覧で -mlx / -nvfp4 を探すと分かります。前提は次の2つです。

  • Apple Silicon搭載のMac(MLXはIntel Macでは動かず、macOS 13.5以降が必要)
  • Apple Silicon対応のOllama(v0.19以降)

まとめ

MLXは推論専用のレイヤーではなく、Apple Siliconの統合メモリと一緒に設計された機械学習フレームワーク全体だ、というのが分かりました。学習・ファインチューニングまでMac上で扱える土台を持っているという意味で、llama.cppの後継というよりは、棲み分けの違うレイヤーに近いと感じます。

M2 MaxとOllama 0.30.10で実際に動かしてみると、MLX(NVFP4)とGGUF(Q4_K_M)の差は事前に想像していたよりずっと小さく、デコードはほぼ並び、プリフィルもGGUFが少し速い程度に収まっていました。Ollama側でMLX安定化のアップデートが継続して入っており、現時点ではどちらを選んでも大きく外さない印象です。

その上で、メモリを抑えたい・既定で広いコンテキストを取りたいケースではMLX、配布されているモデルの幅広さや非Apple環境への展開を考えるならGGUF、と分けると判断しやすいです。M5系のチップが手元にあれば、公式ブログのベンチに近いMLXの伸びを引き出せる可能性がありますが、M2 Max程度の世代ではどちらを選んでも体感は近いというのが今回の率直な感想です。

誰かの参考になれば幸いです。


参考リンク:

この記事をシェアする

関連記事