はじめに
画像を見て会話し、観測した内容を記憶するローカルAIシステムを開発しています。以下では、このシステムを「むつきちゃん」と呼びます。
M4 ProからM5 Maxへ移行したところ、モデルがメモリに載るにもかかわらず、生成速度が期待より遅い問題に遭遇しました。実行経路を切り分けて高速化した後、次の疑問も実験しました。
- 30B級のDenseモデルを実用速度で使えるか
- GPUにANE(Apple Neural Engine)やCPUを加えると速くなるか
- 12B程度のDenseや画像理解向けモデルなら、速度と品質を両立できるか
今後のモデル選びやMac購入時に参照できるよう、結果と限界をまとめます。検証はCodexの支援を受け、ローカルに保存した測定結果とログを確認して行いました。
検証日:2026年9月23〜24日
特定のモデル・量子化・ライブラリ・入力に対する限定試験です。M5 Max全般の性能上限や、各モデルの総合的な優劣を確定したものではありません。
先に結論
- 現行Gemma 4 26B A4B・8bitは、生成経路の変更後に約83〜86 tok/sを確認できました。
- 30B級Dense・8bitは、今回の通常MLXでは約14〜17 tok/sでした。 メモリに載ることと、実用速度で動くことは別でした。
- 12B Dense・8bitは約35〜36 tok/sで動きました。 ただし、今回の画像判定では現行MoEからの品質改善を確認できませんでした。
- 今回試したANE入力処理の分担とCPU併用は、採用につながる高速化になりませんでした。
- Core ML自体の生成速度は未測定です。 ANE併用試験の結果を、Core MLの性能として扱ってはいません。
最終的には、現行MoEを維持しました。
検証環境と採用条件
| 項目 | 内容 |
|---|---|
| 主な検証機 | M5 Max、ユニファイドメモリ64GB |
| 比較・別処理用 | M4 Pro、ユニファイドメモリ64GB |
| OS | macOS 27.0、build 26A428 |
| 推論サーバー | oMLX 0.6.4 |
| 同梱ライブラリ | MLX 0.32.0、mlx-lm 0.31.3、mlx-vlm 0.6.3 |
| Python | oMLX同梱のPython 3.11 |
| 量子化 | 今回実測した候補は8bit |
| 主な画像入力 | 保存済みの3840×2160ゲーム画面PNG |
採用条件は、単にベンチマークの数字が良いことではなく、次の条件が必要としました。
- 実際の画像・プロンプトで、生成速度が最低30 tok/s程度あること
- 最初の応答までの待ち時間と、回答完了までの時間も許容できること
- 現在使っているモデルより、画像の誤認識や判断の誤りを減らせること
元の目的は、大きなDenseモデルを使うこと自体ではなく、実用性を保ちながら判断能力を改善することです。
1. M5 MaxなのにM4 Proより生成が遅かった
同じQwen3-VL-30B-A3B-Instruct・8bitをoMLXの組込みベンチマークで比較したところ、次の結果になりました。
| 入力/出力 | M4 Pro:入力処理 | M5 Max:入力処理 | M4 Pro:生成 | M5 Max:生成 |
|---|---|---|---|---|
| 4096/128 tokens | 814.6 tok/s | 3845.6 tok/s | 48.6 tok/s | 13.1 tok/s |
| 16384/128 tokens | 511.6 tok/s | 3062.3 tok/s | 33.9 tok/s | 10.6 tok/s |
入力処理はM5 Maxの方が速い一方、生成だけが大きく遅くなっていました。
両機のoMLXは0.6.4、SSDキャッシュ上限は100GBでした。関連ソースやMLXバイナリ等の一致も確認しました。ただし、すべての実行条件やドライバー内部の動作まで同一と証明したわけではありません。
通常生成と、バッチ1件の比較
同じロード済みモデル、同じ入力token IDs、greedy、各128出力tokensで、通常生成→バッチ生成→通常生成の順に実行しました。HTTPやoMLXのスケジューラーは通していません。
| モデル・入力 | 通常生成・前 | BatchGenerator・1件 | 通常生成・後 |
|---|---|---|---|
| Qwen3-VL-30B-A3B・8bit、文字4096tokens | 82.72 | 13.84 | 81.47 |
| Gemma 4 26B A4B・8bit、画像+文字3387tokens | 81.86 | 42.32 | 81.86 |
単位は生成tok/sです。どちらのモデルも、それぞれの3回で出力128token IDsが完全一致しました。
Gemmaでは画像を一度だけ処理し、同じ画像+文字embeddingを各経路へ渡しました。キャッシュは各回新規とし、MTPやSSD prefixキャッシュは使用していません。
この結果から、今回の構成では、バッチサイズを1にしても通常生成とは同じ速度にならないことが分かりました。低速化は同梱mlx-lmのBatchGeneratorを直接使った試験でも再現しており、oMLXのHTTP処理だけの原因ではなかったようです。
なお、具体的な単一カーネルや修正すべき演算まで根因を確定したわけではありません。
この初期128token試験の生成速度は、出力token数を初回token以降の時間で割っています。厳密なtoken間隔ベースの計算より約1%弱高めになります。後述の独立試験では主に
(出力数−1) / (最後−最初のtoken時刻)、標準mlx-vlm試験では同ライブラリのgeneration_tpsを使いました。小数点以下まで横断的に順位付けするための数字ではありません。
2. 現行MoEは通常生成経路で約84 tok/sになった
対象Gemmaだけを、通常のgenerate_stepとリクエスト単位のキャッシュで処理する限定的な経路を作成しました。
- oMLXのAPI・モデル管理・画像前処理を利用します。
- 同じモデルへの要求は排他し、1件ずつ処理します。
- 停止文字列、取消、待機、稼働表示、モデル解放を扱います。
- 適用できない機能は、排他制御の中で元の経路へ戻します。
- バッチ用prefix KVキャッシュは、この通常生成経路へそのまま流用しません。
確認できた値は次のとおりです。
| 確認内容 | 出力tokens | 生成速度 |
|---|---|---|
| 本番API・原寸画像+正本用プロンプト | 131 | 85.78 tok/s |
| 実際の画像説明関数からの保存なし試験 | 791 | 83.63 tok/s |
| 隔離HTTP試験・JSON文法制約付き | 条件による | 約80〜81 tok/s |
791tokensの例では、画像処理込みのサーバー時間が10.58秒、呼出し全体が10.85秒でした。生成区間の83.63 tok/sと、全所要から計算する速度は別です。
oMLXアプリ本体は直接書き換えず、起動時の追加コードとして適用しました。関連する実装ファイルが変わった場合は、未検証の追加コードを使わず元の経路へ戻すチェックを設けています。
回避策の限界
これは全モデル向けの完成した修正ではないです。並列要求の処理能力やprefixキャッシュ再利用を、一件の生成速度と引き換えにする面があります。今回の経路はMTPとの併用を検証しておらず、MTPを有効にすれば約84 tok/sへさらに加速が乗るわけではありません。
この観測と回避方法は、oMLX Issue #3882へ報告しました。報告時点では、この環境での再現結果として記載しています。
3. 30B級Dense・8bitは実用速度に届かなかった
次に、アクティブパラメータ数の多いDenseモデルを試しました。
| モデル | 主な測定条件 | 生成速度 |
|---|---|---|
| Gemma 4 31B・8bit | 文字のみ | 14.67 tok/s |
| Gemma 4 31B・8bit | 原寸画像+正本プロンプト | 14.06 tok/s |
| Gemma 4 31B・8bit | 原寸画像+詳しい説明 | 14.21 tok/s |
| Qwen3.8-27B・8bit | 文字約2K入力、GPU通常生成 | 約17.2 tok/s |
Gemma 31Bの画像試験は、画像soft token設定を現行MoEと同じ1120にしました。最大MLXメモリは約37GBで、64GBのMacにロードして生成すること自体はできました。
しかし、私の用途で求める最低30 tok/sには届きませんでした。
これは、今回の8bit・非投機的生成の結果です。4bit、MTP、別のカーネルやモデルの結果まで否定するものではありません。
4. ANEを加えたら速くなるか
Qwen3.8-27B・8bitに対して、oMLX同梱の実験的なANE/GPU分担機能を使いました。
これはCore MLへ変換したモデルの測定ではありません。非公開ANE APIを使い、入力処理の一部をANEへ分担する方式です。文章の生成部分はGPUのままです。
同じ2049入力tokens、128生成tokens、MTPなしで測定しました。
| 条件 | 最初のtokenまで | 生成速度 | 全所要 |
|---|---|---|---|
| GPUのみ・温まった対照 | 2.66秒 | 17.19 tok/s | 10.05秒 |
| GPU+ANE・32層のMLP出力を50%分担 | 3.26秒 | 17.31 tok/s | 10.60秒 |
8層・25%分担も試しましたが、GPU単独との差はほぼありませんでした。
native profileでは32層の条件で31回のANE処理を確認しました。「設定だけONになり、実際にはANEを使っていなかった」という結果ではありません。
また、同じ設定のANE試行同士では出力tokenが一致しましたが、GPU単独とは出力文が異なりました。近似演算を含む経路なので、同じ8bit checkpointでも数値計算が完全に同じとは限りません。
今回の約2K入力・指定分担率では高速化を確認できませんでした。
5. CPU+GPUではどうだったか
同じQwen3.8-27B・8bitで、全64層のMLPのgate/up投影の一部をCPUへ回しました。入力処理だけでなく生成中にも分担させた、独立した実験です。
CPU側は8bit重みの一部をFP16行列へ展開するため、演算入力やscales/biasesの形式を揃えたGPU単独を対照にしました。packed weightsは8bitのままですが、全演算INT8という意味ではありません。
条件は2049入力tokens、64生成tokens、CPU側8 workersです。「10%/25%」は対象投影の出力行の割合であり、モデル全体の処理時間や層数の割合ではありません。
| 条件 | 最初のtokenまで | 生成速度 | 全所要 |
|---|---|---|---|
| GPUのみ・演算形式を揃えた対照 | 2.65秒 | 17.22 tok/s | 6.31秒 |
| CPU10%+GPU | 7.03秒 | 5.63 tok/s | 18.21秒 |
| CPU25%+GPU | 15.78秒 | 2.92 tok/s | 37.37秒 |
| GPUのみへ戻した後 | 2.72秒 | 16.25 tok/s | 6.60秒 |
この実装では逆効果でした。MLXのピークメモリも、対照の約37.55GBから最大約52.33GBへ増えました。これはCPU・OS・ANEドライバー等を含む総メモリではありません。
CPUとGPUを併用できることと、単一のLLM生成が速くなることは別でした。
6. 小型Denseと画像理解向けモデルも試した
30B級が遅かったため、8〜14B級の8bitモデルへ範囲を変えました。
速度の結果
| モデル | 画像付き生成速度 | 最初のtokenまで | 主な注意点 |
|---|---|---|---|
| Gemma 4 12B Unified | 35.5〜35.7 tok/s | 約0.8〜2.3秒 | 画像上限1120を実processorで確認 |
| Qwen3-VL-8B-Instruct | 49.1〜50.8 tok/s | 約15.6〜17.2秒 | 原寸4K画像の入力処理が重い |
| Ministral 3 14B Instruct | 32.2〜32.8 tok/s | 約2.0〜3.9秒 | 内部processorの長辺設定は1540 |
この表は同じ生成文を使った厳密な性能ランキングではありません。 同じ画像・質問を使っても、モデルによって内部画像token数、出力長、前処理方式が異なります。長い説明の一部は出力上限に達しています。
外部で画像を縮小・切り抜きしてはいませんが、モデル内部の前処理によるリサイズはあります。「原寸ファイルを渡した」と「全モデルが同じ画素数・同じ画像token数を処理した」は区別する必要があります。
特にQwen3-VL-8Bは、生成区間の数字だけなら速くても、最初の文字が出るまで待たされました。音声応答や対話用途では、ここを無視できません。
品質の結果
主にゲーム画面の説明をReviewer形式で評価しました。例えば、正しい場面説明を受け入れるか、敵名の取り違えや技名を人物名とする誤説明を拒否できるか、という比較です。
| 比較 | 結果 |
|---|---|
| Gemma 12B対現行MoE:FF14画像の4候補 | 両方とも期待する判定との一致は3/4 |
| Qwen3-VL-8B対現行MoE:3画像・8候補 | 両方とも7/8 |
| Ministral 14B | 出力形式の問題で品質比較を完了できず、点数は未確定 |
Gemma 12BとQwen3-VL-8Bでも、改善したかった「チョコリジェネという技名を、他プレイヤー名として追認する」誤りが残りました。
これらの件数は小さく、同点だから品質全般が同等という意味ではありません。 判定理由の一貫性や画像の根拠も確認し、判定ラベルだけで勝敗を決めないようにしました。
JSON出力の問題は、画像理解の誤りと分けました
Qwen3-VL-8Bは今回の文法制約との組合せで反復し、JSONが完結しませんでした。同じ例を文法制約なしで試すと正常なJSONを返したため、その後の8例比較は両モデルとも「promptでJSON指定+受信後の形式検証」に揃えました。本番の制約は変更していません。
Ministralは、文法制約なしではJSON文字列内の未エスケープ改行、JSON文法制約ありでは必須項目名の崩れが発生しました。後者は任意JSONの文法制約であり、fieldを固定する厳密なJSON Schemaを試したわけではありません。
形式エラーを「画像を理解できなかった」と採点するのは不適切なので、Ministralの品質は未確定としました。ただし、実システムへ採用するうえでは、安定して結果を受け取れることも必要です。
7. 試験側の不備もあった
比較条件の確認不足によるやり直しもありました。
- Gemma 31Bで、画像設定をモデル読込み後に変え、位置情報とのサイズ不一致が起きました。読込み前に揃えて修正しました。
- Gemma 12Bでは、設定ファイルを変えてもローダーが画像processorを既定値で置換していました。実際のprocessor値と入力token数を確認して再試験しました。
- 品質fixtureのゲーム名情報に必須fieldが足りず、promptへ入っていない試行がありました。保存済み情報から正しい形式で構築し直しました。
- 正常なコード枠付きJSONを、試験側の厳密なJSON.parseだけで失敗扱いにした例がありました。本番と同じコード枠除去へ揃えました。
設定ファイルに値があることではなく、実際にモデルへ渡った入力を確認することが重要でした。 不備のある試行は採用判断から除外し、正常な速度測定や対照結果は再利用できる範囲で再利用しました。
8. Core MLと複数Mac分散は、今回は未測定
Core MLによる30B級Denseの高速化も調べましたが、今回の採用条件に合う実測根拠を確認できず、大がかりな移植には進みませんでした。
「Core MLでも速くならなかった」という実験結果ではありません。 Core MLと、今回のoMLX経由のANE分担は別です。
また、MLXには複数Macでのテンソル並列・パイプライン並列がありますが、今回のM5 Max+M4 Proでは実測していません。現在行っているProducerとReviewerの役割分担は、1つのモデルを分割する分散推論とは異なります。
今後のモデル選び・Mac選びで確認したいこと
モデルを選ぶとき
- 実際の画像とpromptで30 tok/sを超えるか
- TTFTと全所要は許容できるか
- 正しい説明の棄却、誤説明の追認、見えない情報の断定が減るか
- JSONや終了条件まで、実際の接続方式で安定するか
- 長い入力、連続処理、他のサービス稼働中でも性能を保てるか
Macを選ぶとき
- メモリ容量だけでなく、使いたいモデルの実測を確認します。
- GPUの総合ベンチマーク倍率を、そのまま生成tok/sの倍率にしません。
- 入力処理と生成では、改善する割合が異なることを考慮します。
- メモリ帯域、モデル形式、量子化、推論エンジンの対応も確認します。
- 複数台に分ける場合は、単独回答の速度と全体の処理量を分けて評価します。
最終判断
今回、現行MoEの生成経路を改善し、約83〜86 tok/sを確認できたことは成果でした。一方、30B級Dense・8bitは速度条件に届かず、小型Dense/VLMでも置換を正当化する品質改善までは得られませんでした。
試験用に追加したモデル5件は削除し、測定結果・試験コード・本番の高速化経路を残しました。本番の役割分担は維持しています。
「大きいモデルが載るMac」ではなく、「目的の品質を、必要な速度で出せるモデルと実行環境」を選ぶ。 今回は、その判断に使える基準を得る検証になりました。
参考リンク
- oMLXへの報告:Issue #3882
- Gemma 4公式モデルカード
- Qwen3-VL-8B-Instruct公式モデルカード
- Ministral 3 14B Instruct公式モデルカード
- MLX:Unified Memory
- MLX:Distributed Communication
- Apple:On Device Llama 3.1 with Core ML
取得した試験モデルのリビジョン
| モデル | Hugging Faceリビジョン |
|---|---|
| mlx-community/gemma-4-31b-it-8bit | f5f3dc92ab4af76724c36c21eb6bedadb3a851be |
| mlx-community/gemma-4-12B-it-8bit | 200bb6db075e137a4deb08838865ac4ddb86292e |
| mlx-community/Qwen3.8-27B-8bit | 815b83c0df8ffd1d1b5244cf75fd6ef14fca9ef9 |
| mlx-community/Qwen3-VL-8B-Instruct-8bit | a0093b9b5fda6f76ddd4a462c6830ae7c4fe47ec |
| mlx-community/Ministral-3-14B-Instruct-2512-8bit | bfd91e844d7204c0950e7a21824388bf911bc7aa |