PR|この記事にはアフィリエイト広告を含みます。
Mac(MacBook Pro M1 Max 64GB)で「画像も読めるローカルLLM」を動かそうとした話。
HuggingFaceに -MLX って付いたリポジトリがあったから、Macなんだしそれでしょ、と思って落とした。テキストはちゃんと喋る。で、画像を投げたら読めない。
原因を追いかけたら、公式のMLX変換から vision tower が丸ごと抜けていた。しかもモデル名でもHFのタグでもアーキテクチャ名でも見抜けない。
結局そのあと自分で量子化し直したんだけど、visionが戻ってきた上に decodeが1.6倍、prefillが1.8〜3倍になった。サイズは1.5GBちょっと増えたけど、それは払う価値のある差だった。
この記事はその一部始終と、他のモデルでも使い回せる確認手順。あと「じゃあMLX版は全部ダメなの?」に答えるために、他のVLM 20組ぶんのconfigを実際に引いて調べた結果も入れてる。
先に結論だけ
-MLXが付いていても、vision(画像を読む部分)が入っているとはかぎらない- モデル名・HFのタグ・
architecturesのどれでも判別できない。vision_start_token_idみたいなトークン設定まで残るので、余計に紛らわしい - 見分ける方法は2つだけ。
config.jsonにvision_configがあるかと、model.safetensors.index.jsonに vision の重みがあるか - ただしMLX版が全部ダメなわけじゃない。20組調べて、visionが消えていたのは4本。しかも全部おなじ発行元のおなじ世代だった
- 自分で量子化し直せば戻る。M1/M2 は float16、M3/M4 は bfloat16 を選ぶのがポイント
何が起きていたか
使ったのは Ornith-1.5-35B-A3B。35BのMoEでアクティブ3Bっていう、Macと相性のいいタイプ。画像も読めるVLMとして出てる。
HFには本家とMLX版の両方がある。中身を並べるとこう。
| ornith-ai/Ornith-1.5-35B-A3B | ornith-ai/Ornith-1.5-35B-A3B-MLX | |
|---|---|---|
| サイズ | 67.0 GB | 64.6 GB |
| ファイル数 | 33 | 24 |
config.json の vision_config |
あり | 無い |
visual. で始まる重み |
333個 | 0個 |
| 総テンソル数 | 1,811 | 733 |
差の2.4GBがvision tower。変換の過程でまるごと落ちてる。
ちなみにこれ、mlx-community みたいな有志の変換じゃなくて、モデルを出してる ornith-ai 本人のリポジトリ。-MLX / -MLX-4bit / -MLX-6bit / -MLX-8bit と4種類あるけど、全部おなじアカウントから出てる。
で、MLX版のmodel cardにはvisionを外したって書いてない。「~35BのMoE、活性3B、コーディングとエージェント向け」としか書かれていなくて、画像・マルチモーダル・text-only のどれにも触れていない。だから読んでも気づけない。
名前でもタグでもアーキ名でも見抜けない
ここが今回いちばん厄介だったところ。

① アーキテクチャ名が同じ
両方こう。
"architectures": ["Qwen3_5MoeForConditionalGeneration"]
ForConditionalGeneration ってマルチモーダルによく付く名前なんだよね。だからこれを見て「お、VLMだ」と判断すると、そのまま罠にハマる。
② vision関連のトークンIDが残ってる
MLX版の config.json にも、これがそのまま入ってる。
| キー | 本家 | MLX版 |
|---|---|---|
vision_config |
あり | 無い |
image_token_id |
あり | あり |
video_token_id |
あり | あり |
vision_start_token_id |
248053 | 248053 |
vision_end_token_id |
248054 | 248054 |
消えてるのは vision_config ブロックだけ。トークナイザ側の設定だけ残って、画像を処理する重みが無い状態。config.jsonをざっと眺めて「vision って単語あるし大丈夫でしょ」で終わると見逃す。
③ HFのタグも当てにならない
本家 ornith-ai/Ornith-1.5-35B-A3B は、tags に image-text-to-text が入ってるのに pipeline_tag は text-generation。逆に bartowski や AtomicChat が出してるGGUF版は image-text-to-text になってる。
タグは投稿者が手で付けるものだから、こういうズレが普通に起きる。補助情報どまりで、判断材料にはできないと思っておいたほうがいい。
確実な見分け方(ここだけ持って帰ってほしい)
3つある。上から順にラク。
いちばんラク:ツールの表示を見る
これ、後から気づいたんだけど、oMLXは読み込む時点で vlm か llm かを勝手に判定してバッジで出してくれてる。手元に3つ並べるとこうなる。

llm と判定されている| モデル | oMLXの判定 | サイズ表示 |
|---|---|---|
| Ornith-1.5-35B-A3B-oQ4e-fp16(自分で作ったやつ) | vlm | ~20.55 GB |
| ornith-ai/Ornith-1.5-35B-A3B(本家・未量子化) | vlm | 70.31 GB |
| ornith-ai/Ornith-1.5-35B-A3B-MLX-4bit(公式MLX版) | llm | 19.08 GB |
公式のMLX-4bitだけ llm になってる。ツール側も「これは画像を読めないモデル」と判定してるということ。config.jsonを開かなくても、この3文字で分かる。
正直これがいちばん早い。ただし落としたあとにしか分からないのが弱点で、60GBをダウンロードしてから気づくことになる。事前に知りたいなら下の2つ。
ステップ1:config.json に vision_config があるか
https://huggingface.co/<ユーザー名>/<モデル名>/raw/main/config.json
これをブラウザで開いて、vision_config で検索(Cmd+F)。ブロックごと無ければ、そのモデルは画像を読めない。
注意点として、vision_start_token_id や vision_end_token_id はヒットしても意味がない。探すのは "vision_config": { というブロックのほう。
ステップ2:重みに vision が入っているか
https://huggingface.co/<ユーザー名>/<モデル名>/raw/main/model.safetensors.index.json
こっちを開いて visual. または vision_tower. で検索。ヒット0なら重みが入っていない。
命名はモデルによって違う(model.visual.* だったり vision_tower.* だったり)ので、両方試すのが早い。ファイルが分割されていないモデルだと index.json 自体が無いこともあって、その場合はステップ1で判断する。
この2つ、ダウンロードする前に確認できるのがいいところ。60GBを落としてから気づくのはさすがにつらい。
じゃあMLX版は全部ダメなの? 20組調べた
ここ、正確に書いておきたい。そんなことはない。
気になったので、他のVLMについても「本家」と「MLX変換版」のペアを作って、config.jsonを実際に取得して比べてみた。2026年8月21日時点で20組。
| 元モデル | MLX版 | vision_config |
|---|---|---|
| Qwen/Qwen2-VL-7B-Instruct | mlx-community/Qwen2-VL-7B-Instruct-4bit | 保持 |
| openbmb/MiniCPM-V-4_6 | mlx-community/MiniCPM-V-4.6-4bit | 保持 |
| microsoft/Phi-3.5-vision-instruct | mlx-community/Phi-3.5-vision-instruct-4bit | 保持 |
| ibm-granite/granite-vision-3.2-2b | mlx-community/granite-vision-3.2-2b-4bit | 保持 |
| HuggingFaceM4/Idefics3-8B-Llama3 | mlx-community/Idefics3-8B-Llama3-4bit | 保持 |
| HuggingFaceTB/SmolVLM-Instruct | mlx-community/SmolVLM-Instruct-4bit | 保持 |
| zai-org/GLM-4.1V-9B-Thinking | mlx-community/GLM-4.1V-9B-Thinking-4bit | 保持 |
| zai-org/GLM-4.5V | mlx-community/GLM-4.5V-4bit | 保持 |
| OpenGVLab/InternVL3-8B-hf | mlx-community/InternVL3-8B-bf16 | 保持 |
| deepreinforce-ai/Ornith-1.0-9B | mlx-community/Ornith-1.0-9B-4bit | 保持 |
| ornith-ai/Ornith-1.5-35B-A3B | ornith-ai/Ornith-1.5-35B-A3B-MLX | 欠落 |
| ornith-ai/Ornith-1.5-35B-A3B | ornith-ai/Ornith-1.5-35B-A3B-MLX-4bit | 欠落 |
| ornith-ai/Ornith-1.5-9B | ornith-ai/Ornith-1.5-9B-MLX | 欠落 |
| ornith-ai/Ornith-1.5-9B | ornith-ai/Ornith-1.5-9B-MLX-4bit | 欠落 |
で、結果はこう。
- visionが消えていたのは4本。全部 ornith-ai 公式の「Ornith-1.5」世代(35B-A3B と 9B の、bf16版と4bit版)
- 4本ともREADMEに断り書きが無い
- おなじ Ornith でも、1.0世代(mlx-community が変換したほう)は vision が残ってる
- mlx-community が出してるものは、調べた範囲では全部保持されていた
つまり「MLXという形式の問題」じゃなくて、「特定の発行元の、特定の世代の変換パイプラインの問題」ということになる。MLX変換そのものを疑う必要はないと思う。
もうひとつ別の罠:skip_vision フラグ
調べてる途中で、これとは違うパターンも見つけた。
mlx-community/Qwen2.5-VL-7B-Instruct-4bit は vision_config ブロック自体はある。でもその中に "skip_vision": true というフラグが立ってる。リポジトリ名にもREADMEにも、それについての説明は無い。model.safetensors.index.json のほうには vision_tower.* の重みキーが存在している。
このフラグが実際の推論でどう効くのかは自分では検証できていない。変換ツールの内部フラグなだけかもしれないし、本当にvisionを通さない指定なのかもしれない。ここは断言できないので、「そういうフラグがあるモデルもある」というところまでにしておく。
ちなみに mlx-community/SmolVLM2-500M-Video-Instruct-mlx-8bit-skip-vision みたいに、リポジトリ名で最初から宣言してくれてる例もある。こういうのはありがたい。
で、自分で量子化した
本家(vision入り・67GB)を落としてきて、oMLX の oQ で変換した。
設定

| 項目 | 選んだ値 | 理由 |
|---|---|---|
| ソース | Ornith-1.5-35B-A3B(67GB) | vision入りのほう |
| OQレベル | oQ4e | 実効 4.8bpw |
| Enhanced quantization(oQe) | ON | UIに「especially for MoE」と書いてある |
| Text Only | OFF | ★ONにするとvisionが落ちる |
| Non-quant weight dtype | float16 | ★M1だから。次で説明 |
画面の上のほうに Estimated memory: ~105.4 GB / Effective 4.8 bpw / Output size: ~19.7 GB と出てる。この105.4GBが後で効いてくる。
Text Only が地味に大事で、ここをONにすると自分でおなじ罠を再生産することになる。せっかくvision入りを落としてきた意味がなくなる。
あと Preserve MTP weights というトグルもあって、ONにすると mtp.* テンソルとconfigが出力側に残って、量子化後もMTP(投機デコード)が使えるようになる。出力名に -mtp が付く仕様。今回はここをOFFのままにしたので、ONにしてたらもう少し伸びた可能性がある。次はそれを試したい。
Non-quant weight dtype で float16 を選ぶと、出力名に -fp16 が自動で付く。だから今回できたモデルの名前が Ornith-1.5-35B-A3B-oQ4e-fp16 になってる。
★ M1/M2 は float16、M3以降は bfloat16
これ、知らなかったので面白かったところ。oMLXのUIにこう書いてある。
float16 gives ~20% faster prefill on M1/M2 Apple Silicon (native fp16). bfloat16 is safer on M3/M4 and for numerical stability.
気になったのでApple側の資料も見にいった。ここ、誤解しやすいので先に書いておく。「M1では bfloat16 が動かない」わけじゃない。
bfloat という型自体は Metal Shading Language 3.1 で導入されていて、GPUファミリーでいうと Apple6(A13)以降で使える。M1は Apple7 なので、型としては普通に通る。世代の対応はこう。
| GPUファミリー | チップ |
|---|---|
| Apple7 | M1系 |
| Apple8 | M2系 |
| Apple9 | M3系・M4系 |
| Apple10 | M5系 |
で、「使える」と「速い」は別の話。fp16 は昔からApple Siliconが得意な形式で、bf16 のハードウェア対応は後から乗った。oMLXが「M1/M2ではfp16のほうが約20%速い」と書いているのは、たぶんこの世代差の話。
そもそも fp16 と bf16 は何が違うのか
どちらも16bit。同じ16bitをどう配るかが違う。
| 形式 | 指数部(表せる範囲) | 仮数部(細かさ) |
|---|---|---|
| float32 | 8bit | 24bit |
| float16 | 5bit | 11bit |
| bfloat16 | 8bit(float32と同じ) | 8bit |
つまり bf16 は float32 と同じ広さの範囲を持っていて、そのぶん細かさを捨ててる。だから桁が振り切れて壊れにくい。UIが「safer for numerical stability」と書いてるのはここ。逆に fp16 は範囲が狭いぶん細かい。
速さを取るか、壊れにくさを取るかという話で、M1/M2 は速さ側に fp16 という答えがある、ということになる。
で、ここが効いてくる理由がもうひとつあって、prefill は量子化レベルに関係なく非量子化の重みの上で走る。つまり4bitモデルを作っても、この dtype の差はそのまま受ける。チップの世代で正解が逆転するのはちょっと珍しいと思う。
ひとつ正直に書いておくと、自分が作ったのは fp16 版だけで、同じモデルの bf16 版を作って測り比べてはいない。だから「M1ではfp16が約20%速い」は oMLX 側の主張であって、自分の実測じゃない。手元で確かめたのは「fp16で作ったらちゃんと速かった」というところまで。
それでも、M1 Max / M2 Max あたりを使ってる人は float16、M3以降なら bfloat16、という指針は覚えておいて損はないと思う。
「Estimated memory ~105.4 GB」は脅しだった
変換を始めようとしたら、UIがこう出してきた。
105.4GB必要。手元は64GB。普通にビビる。
でも押した。結果、13分(764秒)で完走した。メモリも余裕だった。
あとで仕組みを読んだら、oQ側がちゃんと考えて作られてた。
- テンソルを safetensors からストリーミングで処理する。モデル全体をRAMに載せない
- キャリブレーションは実メモリと Metal working set の小さいほうを使う。容量の75%を超えるチェックポイントには一時的な4bitプロキシを使う
- 残り25%を、モデル実行とimatrix取得のために確保する。16/32/64GBシステムが想定に入ってる
105.4GB っていうのは、ストリーミングを考慮しない最悪値の見積り。この数字だけ見て諦めるのはもったいない。
ただしディスクの空きは要る。macOSのスワップはディスク上に動的に作られるから、ディスクが逼迫してるとスワップを広げられなくて落ちる。ディスクを空けるのはスワップの余地を確保することでもある、というのは覚えておいたほうがいい。
結果:visionが戻って、ついでに速くなった

| 指標 | 公式 MLX-4bit | 自前 oQ4e-fp16 |
|---|---|---|
| decode | 34 tok/s | 54.6 tok/s(1.6倍) |
| prefill | 182.7 tok/s | 329〜548 tok/s |
| vision | 非対応 | 動作 |
| サイズ | 19.08 GB | 20.55 GB |
M1に合わせて自分で量子化し直したら、速度が1.6〜3倍になって、しかも画像が読めるようになった。これは素直に気持ちいい。
ひとつ念のため書いておくと、速くなったのはサイズが1.5GB増えたからじゃない。サイズが増えたのは oQ4e が公式の4bitより少し厚い量子化だからで、速度のほうに効いてるのは、oQe のimatrixキャリブレーションと、M1向けに非量子化重みを float16 にしたところ。順番としては「M1に合わせて作り直したら速くなった。ついでにサイズが1.5GB増えた」が正しい。
visionのほうも実際に試した。交通カメラの画像(1920×548)を投げたら、1,052トークンで処理して車両5台を正しく数えた。ちゃんと動いてる。
文脈も ctx_window: 262144、つまり262Kまで開いた状態で読み込めてる。64GBのMacで、35BのVLMを262K文脈で持てるというのは、けっこうすごいことだと思う。
imatrix の品質指標も見ておく
MoEを量子化するとき、いちばん気にしたいのがここ。
zero_count_experts : 0 ← 全エキスパートに活性化を観測
mismatched : [] ← 不整合ゼロ
applied : 470テンソル
missing : lm_head と embed_tokens の2つのみ
zero_count_experts: 0 は「一度も使われなかったエキスパートが無い」ということ。ここが0じゃないと、使われてないエキスパートの量子化が雑になる。
missing の2つは出力語彙に直結するから高精度で保護される設計で、そもそもimatrixの対象外。落ちてるわけじゃないので慌てなくていい。
量子化しても直らないもの
ここは正直に書いておく。
会社法について質問したら「会社法第2編第1章第2節第30条」と答えてきた。取締役の任期は第332条なので条文番号が違う。「原則2年」という結論のほうは合ってた。
細かい条文番号は量子化の問題じゃなくてモデルの知識の問題だから、量子化をどう工夫しても直らない。法務まわりで使うならRAGで実際の条文に接地させるしかない。
ローカルLLMは「速さ」と「手元で完結すること」を買うものであって、知識の正確さを買うものじゃない、というのは前提にしておいたほうがいい。
で、どのMacならこれができるのか
今回のモデルは量子化後で20.5GB。ここから逆算するとこうなる。
動かすだけなら
20.5GBが載ればいいので、ユニファイドメモリ36GBあたりから現実的。文脈を長く取るとKVキャッシュが乗ってくるので、余裕を見るなら48GB以上。
ちなみに16GBは無理。27B級も35B級も入らないので、ローカルLLM目的なら最初から外していい。
自分で量子化までやるなら
自分は64GBでやった。UIは105.4GB必要と言ってきたけど13分で終わった。ただこれは64GBでの実測なので、36GBや48GBで同じことができるかは試してない。oQ側が16/32/64GBを想定に入れてる以上いけそうな気はするけど、そこは断言できない。
「落としてきて動かす」だけならメモリは控えめでいい。「自分で作る側に回りたい」なら64GBあると安心、というのが今回やってみた感想。
2026年8月時点で買えるMacBook Pro
| モデル | メモリ | 帯域 | 価格 |
|---|---|---|---|
| MacBook Pro 14 M5 | 16GB | 153GB/s | ¥322,637 |
| MacBook Pro 14 M5 Pro | 24GB | 307GB/s | ¥429,800 |
| MacBook Pro 14 M5 Max | 36GB | 460GB/s | ¥677,620 |
| MacBook Pro 16 M5 Max | 48GB | 614GB/s | ¥915,600 |
今回の話でいうと、M5 Max の36GB が「動かす側」の入口。自分みたいに量子化まで回したいなら、公式でメモリを積んだ構成を組むことになる。メモリは後から足せないので、ここは背伸びしておいて損はないと思う。
MacとGPU、そもそもどっちを選ぶかという話は前に書いたので、まだ機材を決めてない人はこっちから読んでもらうと早い。
ローカルLLMはMacとGPUどっちで動かす?|MacBook Pro M1 Max・RTX3090の実測で選ぶ【2026年8月版】
当サイトはAmazonアソシエイト・プログラムの参加者です。適格販売により収入を得ています。
まとめ
-MLXが付いていてもvisionが入っているとはかぎらない。名前・タグ・アーキ名では見抜けない- 手元にモデルがあるなら、oMLXの vlm / llm バッジを見るのがいちばん早い
- 落とす前に確認するなら2つ。
config.jsonのvision_configブロックと、model.safetensors.index.jsonのvisual./vision_tower.。落とす前にブラウザで見れる - ただしMLX変換そのものを疑う必要はない。20組調べて欠落は4本、全部おなじ発行元のおなじ世代だった
skip_vision: trueという別パターンもある。ここは自分では未検証- 自分で量子化すればvisionは戻る。Text Only は OFF、M1/M2は float16 / M3以降は bfloat16
- メモリ見積りの表示は最悪値。64GBで13分で終わった。ディスクの空きはスワップの余地でもある
- M1向けに自分で量子化し直したら decode 1.6倍、prefill 1.8〜3倍、しかもVLMになった。サイズは1.5GBちょっと増えた
- 知識の誤りは量子化では直らない。条文番号みたいな話はRAGの領域
速度・サイズ・メモリまわりの数値は、MacBook Pro M1 Max 64GB / oMLX 0.5.7 で 2026年8月21日に計測した単発(n=1)の実測で、統計的に均した値ではない。HuggingFace上の config.json / README の内容も同日時点のもので、リポジトリは更新される可能性がある。判断の前に自分の環境と最新の状態を確認してほしい。

