MacのMLX版で画像が読めない原因|公式変換でvisionが落ちていた話と、自分で量子化して1.6倍にした実測【2026年8月】

AI活用

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.jsonvision_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
サイズ・ファイル数・テンソル数は自分の手元で確認した値。config.json の中身は 2026年8月21日時点でHuggingFace上から取得して確認

差の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 のどれにも触れていない。だから読んでも気づけない。

名前でもタグでもアーキ名でも見抜けない

ここが今回いちばん厄介だったところ。

本家とMLX版のconfig.json比較表。architectures、image_token_id、video_token_id、vision_start_token_id、vision_end_token_idは両方一致し、vision_configだけが本家にはあり公式MLX版には無い
上の5行が完全に一致するので、名前やタグでは見分けられない。違うのは最下段の1行だけ

① アーキテクチャ名が同じ

両方こう。

"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
2026年8月21日時点で両リポジトリの config.json を取得して比較

消えてるのは 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つ並べるとこうなる。

oMLXのモデル一覧。Ornith-1.5-35B-A3B-oQ4e-fp16 と ornith-ai/Ornith-1.5-35B-A3B には vlm バッジ、ornith-ai/Ornith-1.5-35B-A3B-MLX-4bit には llm バッジが付いている
oMLX v0.5.7 のモデル一覧。公式のMLX-4bitだけ 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
oMLX v0.5.7 のモデル一覧での表示。サイズはツールが出している数字なので、HuggingFace上の表示や oQ の画面とは端数が少し違う

公式の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_idvision_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 欠落
2026年8月21日時点で各リポジトリの config.json を取得して確認。表は主要な14組を抜粋(Llama-3.2-11B-Vision / aya-vision-8b は元モデルが gated で取得できず、Pixtral-12B は 404 だったため判定対象外)

で、結果はこう。

  • 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-4bitvision_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 で変換した。

設定

oMLXのoQ量子化画面。ソースモデルはOrnith-1.5-35B-A3B(67.0GB)、OQレベルはoQ4e、Estimated memory ~105.4GB、Text OnlyはOFF、Non-quant weight dtypeはfloat16を選択
oMLX v0.5.7 の oQ量子化画面。Text Only を OFF、Non-quant weight dtype を float16 にしたところ
項目 選んだ値 理由
ソース 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系
Apple の Metal Feature Set Tables より

で、「使える」と「速い」は別の話。fp16 は昔からApple Siliconが得意な形式で、bf16 のハードウェア対応は後から乗った。oMLXが「M1/M2ではfp16のほうが約20%速い」と書いているのは、たぶんこの世代差の話。

そもそも fp16 と bf16 は何が違うのか

どちらも16bit。同じ16bitをどう配るかが違う。

形式 指数部(表せる範囲) 仮数部(細かさ)
float32 8bit 24bit
float16 5bit 11bit
bfloat16 8bit(float32と同じ) 8bit
bfloat については Metal の仕様書に「24bitではなく8bit(明示的に格納されるのは7bit)の仮数部を使う、floatを切り詰めた形式」と書かれている

つまり 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の速度比較。デコードは34から54.6 tok/sで約1.6倍、プリフィルは182.7から329〜548 tok/s。画像の読み取りは非対応から動作へ、サイズは19.08GBから20.55GB、量子化にかかった時間は13分
デコードとプリフィルは目盛りの上限が違うので、2つのグラフの棒の長さを直接見比べないでほしい
指標 公式 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
MacBook Pro M1 Max 64GB / oMLX 0.5.7 で 2026年8月21日に計測。いずれも単発(n=1)の実測で、統計的に均した値ではない

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
価格は2026年8月時点のAmazon.co.jp。M5 Max の2機種は AppleCare 3年が同梱された構成なので、Apple公式の本体だけの価格とは単純比較できない。64GB以上や128GBはAppleのCTO(注文時カスタマイズ)になるので、Amazonの吊るしには出てこない

今回の話でいうと、M5 Max の36GB が「動かす側」の入口自分みたいに量子化まで回したいなら、公式でメモリを積んだ構成を組むことになる。メモリは後から足せないので、ここは背伸びしておいて損はないと思う。

MacとGPU、そもそもどっちを選ぶかという話は前に書いたので、まだ機材を決めてない人はこっちから読んでもらうと早い。

ローカルLLMはMacとGPUどっちで動かす?|MacBook Pro M1 Max・RTX3090の実測で選ぶ【2026年8月版】

当サイトはAmazonアソシエイト・プログラムの参加者です。適格販売により収入を得ています。

まとめ

  • -MLX が付いていてもvisionが入っているとはかぎらない。名前・タグ・アーキ名では見抜けない
  • 手元にモデルがあるなら、oMLXの vlm / llm バッジを見るのがいちばん早い
  • 落とす前に確認するなら2つ。config.jsonvision_config ブロックと、model.safetensors.index.jsonvisual. / vision_tower.。落とす前にブラウザで見れる
  • ただしMLX変換そのものを疑う必要はない。20組調べて欠落は4本、全部おなじ発行元のおなじ世代だった
  • skip_vision: true という別パターンもある。ここは自分では未検証
  • 自分で量子化すればvisionは戻る。Text Only は OFFM1/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 の内容も同日時点のもので、リポジトリは更新される可能性がある。判断の前に自分の環境と最新の状態を確認してほしい。

タイトルとURLをコピーしました