快轉到主要內容
  1. 教學文章/

MLX-VLM 實戰:Apple Silicon 本地圖片問答與多模態模型

·7 分鐘· loading · loading · ·
Mlx MLX-VLM VLM Multimodal Apple-Silicon Local AI Python
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
科技觀點 - 本文屬於一個選集。
§ 27: 本文

featured

一. 前言:讓模型看圖,不只是多傳一個檔案
#

文字模型很會回答問題,但你把一張截圖放在旁邊,它其實什麼都沒看到。

要讓本地模型理解照片、圖表、介面截圖或掃描文件,需要的是 Vision-Language Model(VLM):先把影像轉成視覺特徵,再交給語言模型回答。

在 Apple Silicon 上,mlx-vlm 把這條流程包成可直接使用的 CLI 與 Python API。今天拍拍君會做出一個本地圖片問答工具,並逐步加入:

  • 單張圖片描述與追問
  • 多張圖片比較
  • 可重用的 Python 函式
  • 更可靠的提問格式
  • 多輪問答的 Vision Feature Cache

如果你只想處理純文字,先讀 MLX-LM 本地模型推論;本篇刻意不重複文字聊天、量化或 API server,而是專心處理「影像怎麼進模型」。

二. 先理解 MLX-VLM 的資料流
#

最小流程可以畫成四步:

image -> processor -> vision features -> language model -> answer
                prompt -----------^ 

其中有兩件事很容易搞錯。

第一,圖片不是普通 prompt 附件。模型需要對應的 processor,負責縮放、正規化與建立影像 token。

第二,instruct 模型通常需要自己的 chat template。少了 template,模型可能重複問題、答非所問,甚至無法正確對齊圖片位置。

所以程式不是只有 generate("這張圖是什麼?"),而是:

  1. 載入模型與 processor
  2. 用 chat template 格式化問題
  3. 告訴 template 有幾張圖片
  4. 同時把 prompt 與圖片交給 generate()

三. 安裝:先建立乾淨環境
#

本篇使用 Python 3.10 以上與 uv

mkdir pypy-vlm
cd pypy-vlm
uv init --python 3.12
uv add mlx-vlm pillow

先確認套件與 CLI:

uv run python -c "import mlx_vlm; print('mlx-vlm ready')"
uv run mlx_vlm.generate --help

第一次執行模型時會下載權重。下載量、記憶體需求與授權都取決於模型,正式使用前要到模型頁確認。

教學採用官方文件中的小型 4-bit 範例:

mlx-community/Qwen2-VL-2B-Instruct-4bit

它適合先驗證工作流,不代表永遠是最新或最準的選擇。模型 ID 應該放在設定裡,不要散落在每支程式。

export PYPY_VLM_MODEL="mlx-community/Qwen2-VL-2B-Instruct-4bit"

四. 第一個 CLI 圖片問答
#

準備一張本機圖片:

pypy-vlm/
├── pyproject.toml
├── uv.lock
└── images/
    └── desk.jpg

先請模型客觀描述圖片:

uv run mlx_vlm.generate \
  --model "$PYPY_VLM_MODEL" \
  --image images/desk.jpg \
  --prompt "Describe the visible objects and their positions." \
  --max-tokens 200 \
  --temperature 0.0

temperature 0.0 適合做可重跑的觀察型任務。它不會保證答案正確,但能減少每次執行時的隨機變化。

好的視覺問題通常包含三個元素:

元素 作用 範例
任務 說清楚要觀察什麼 找出可見物件
範圍 限制數量或區域 最多五個、只看右上角
格式 讓結果可讀、可解析 短條列、JSON

只問「這是什麼?」往往會得到一段很有自信、但不一定符合需求的作文。

五. 用 Python API 封裝單圖問答
#

建立 ask_image.py。模型只載入一次,圖片數量則明確交給 template:

import os
from pathlib import Path
from mlx_vlm import generate, load
from mlx_vlm.prompt_utils import apply_chat_template
model_id = os.environ.get(
    "PYPY_VLM_MODEL",
    "mlx-community/Qwen2-VL-2B-Instruct-4bit",
)
model, processor = load(model_id)
def ask_image(image_path: Path, question: str):
    if not image_path.is_file():
        raise FileNotFoundError(f"找不到圖片:{image_path}")
    images = [str(image_path)]
    prompt = apply_chat_template(
        processor,
        model.config,
        question,
        num_images=len(images),
    )
    return generate(
        model,
        processor,
        prompt,
        images,
        max_tokens=256,
        temperature=0.0,
        verbose=False,
    )
answer = ask_image(
    Path("images/desk.jpg"),
    "只描述直接可見的內容;不確定時請明確說明。",
)
print(answer)

執行:

uv run python ask_image.py

模型 ID 可替換,num_images 與實際圖片數一致,而且程式會在載入影像前檢查路徑。這三個邊界能讓後續換模型、改成多圖時少踩雷。

六. 圖片前處理:方向、尺寸與色彩模式
#

手機照片可能帶 EXIF 方向資訊;有些 PNG 可能是 palette 或含 alpha channel。為了讓批次輸入一致,可以先正規化:

from pathlib import Path
from PIL import Image, ImageOps
def normalize_image(source: Path, target: Path) -> Path:
    with Image.open(source) as image:
        image = ImageOps.exif_transpose(image)
        image = image.convert("RGB")
        image.thumbnail((1600, 1600))
        image.save(target, format="JPEG", quality=90)
    return target

這裡用 thumbnail() 限制最長邊,並保留長寬比。不要粗暴壓成固定正方形,否則物件形狀可能被拉壞。

也不要一看到高解析圖片就認為答案一定更準。實際輸入尺寸仍會受 processor 與模型架構限制,超大的原圖只可能增加讀檔與轉換成本。

透明 PNG 轉成 RGB 前最好先鋪白底,避免透明區域變成非預期顏色。前處理要固定,但不要假裝自己比模型的 processor 更懂它需要的像素尺寸。

七. 多圖比較:順序就是資料的一部分
#

mlx-vlm 支援一次傳多張圖片,但是否能穩定多圖推理仍取決於模型。

from mlx_vlm import generate, load
from mlx_vlm.prompt_utils import apply_chat_template
model, processor = load(
    "mlx-community/Qwen2-VL-2B-Instruct-4bit"
)
images = ["images/before.jpg", "images/after.jpg"]
question = """
Image 1 is BEFORE and Image 2 is AFTER.
List only directly visible differences.
For every difference, state the location and your confidence.
""".strip()
prompt = apply_chat_template(
    processor,
    model.config,
    question,
    num_images=len(images),
)
result = generate(model, processor, prompt, images,
                  max_tokens=320, temperature=0.0,
                  verbose=False)
print(result)

不要只寫「比較這兩張」。請明確定義 Image 1 與 Image 2 的角色,並要求位置與信心程度。

批次處理時也要固定排序。glob() 的結果不該直接當語意順序:

from pathlib import Path
images = sorted(Path("images/sequence").glob("*.jpg"))
image_paths = [str(path) for path in images]

最好使用 01-before.jpg02-after.jpg 這類檔名,讓排序規則一眼可見。

八. 多輪追問:別一直重算同一張圖
#

同一張圖片連問三次時,最浪費的做法是每一輪都重新跑 vision encoder。

目前 mlx-vlm 提供 VisionFeatureCache,可以快取已投影的視覺特徵:

from mlx_vlm import VisionFeatureCache, load, stream_generate
from mlx_vlm.prompt_utils import apply_chat_template
model_id = "mlx-community/Qwen2-VL-2B-Instruct-4bit"
model, processor = load(model_id)
vision_cache = VisionFeatureCache()
image = "images/desk.jpg"
def ask(question: str) -> None:
    prompt = apply_chat_template(
        processor,
        model.config,
        question,
        num_images=1,
    )
    for chunk in stream_generate(
        model,
        processor,
        prompt,
        image=[image],
        max_tokens=200,
        temperature=0.0,
        vision_cache=vision_cache,
    ):
        print(chunk.text, end="", flush=True)
    print()
ask("先列出畫面中的主要物件。")
ask("其中哪些物件位於畫面左側?")
ask("哪些細節你無法確定?")

第一次會建立視覺特徵;後續相同圖片可命中 cache。圖片換了,就應該視為新的輸入。

要注意,Vision Feature Cache 不是完整對話記憶。若第二題需要第一題的文字上下文,你仍要自行保存對話,或把必要內容放回 prompt。

九. 讓回答更可靠:把「不知道」設計進 prompt
#

VLM 可能把模糊區域補成合理故事。拍拍君會在重要任務中加入這些約束:

只回報圖片中可直接觀察到的內容。
不要猜測畫面外資訊、人物身份或拍攝地點。
看不清楚時回答「無法確認」。
每個判斷附上位置與簡短依據。

若要機器解析,可以要求 JSON:

Return JSON with this schema:
{
  "objects": [
    {
      "name": "string",
      "location": "string",
      "confidence": "high | medium | low"
    }
  ],
  "uncertain": ["string"]
}

但「要求 JSON」不等於「保證合法 JSON」。應用程式端仍要用 json.loads() 解析並檢查欄位型別;高風險場景更不能只靠模型自述的 confidence,必須搭配人工複核或可追溯原圖的檢查流程。

十. 模型選擇:先看任務,再看參數量
#

選 VLM 時至少檢查:

  1. 是否真的支援圖片,而不是純文字模型
  2. 是否支援多圖、影片或你需要的語言
  3. 權重格式是否可由目前 mlx-vlm 載入
  4. 記憶體是否適合自己的 Mac
  5. 模型授權是否允許預定用途
  6. 模型卡是否說明建議 prompt template

小模型適合先做功能驗證與大量低風險分類;較大的模型通常更有能力處理細節與複雜推理,但下載、記憶體與延遲也會上升。

模型 repo 會更新,正式專案最好記錄:

model_id
model_revision
mlx-vlm version
Python version
test image checksum
prompt
generation parameters

光記「我用 Qwen」幾乎無法重現結果。

十一. 建立一個最小評測集
#

不要只用一張漂亮 Demo 圖就宣布完成。建立一個固定小型資料夾:

eval/
├── easy-object.jpg
├── tiny-text.jpg
├── low-light.jpg
├── crowded-scene.jpg
└── expected.json

expected.json 不一定要有唯一答案,但應列出 must_includemust_not_claimmanual_review 等關鍵檢查點。

每次換模型或升級套件,用相同 prompt 重跑,再比較:

  • 關鍵物件有沒有漏掉
  • 是否憑空增加不存在的物件
  • 小字與模糊區域是否誠實表達不確定
  • 輸出格式是否穩定
  • 首輪與後續追問延遲
  • 峰值記憶體

這比盯著單一 benchmark 排名更接近自己的真實需求。

十二. 隱私與安全:本地不等於自動安全
#

本地推論的優點是圖片不必送往雲端 API,但整條資料流仍要檢查。

  • 遠端圖片 URL 仍會產生網路請求
  • 模型權重第一次需要下載
  • 暫存檔與正規化副本可能保留敏感內容
  • 日誌可能不小心寫入檔名、prompt 或模型回答
  • 第三方模型可能需要 trust_remote_code

處理敏感圖片時,優先使用本機路徑、受控模型、明確的暫存目錄與生命週期。不要因為 terminal 顯示在自己電腦上,就假設中間沒有任何外部連線。

也不要把模型回答當成 OCR、身份辨識或安全審核的唯一依據。VLM 是很有用的分析助手,不是保證正確的感測器。

十三. 常見問題
#

  • 回答一直重複問題: 確認 instruct 模型有經過 apply_chat_template()
  • 模型像是沒看到圖片: 檢查路徑、num_images,以及 checkpoint 是否支援視覺輸入。
  • 第一次特別慢: 下載、載入與第一次編譯都算冷啟動,要和穩態延遲分開量。
  • 追問仍然很慢: 不要重複載入模型;相同圖片使用 VisionFeatureCache
  • 圖片一多就記憶體不足: 減少單次張數、換較小模型並限制輸出 token。

結語
#

mlx-vlm 讓 Apple Silicon 上的圖片問答變得很直接,但真正可靠的應用不只是一行 CLI。

一套健康的流程應該包含:正確的 chat template、明確的圖片順序、可替換的模型設定、輸入正規化、不確定性約束、Vision Feature Cache,以及自己的小型評測集。

先從一張圖、一個具體問題開始。確認模型真的看對,再擴充到多圖、批次與多輪互動。這樣做慢一點點,會比得到一大堆漂亮幻覺省事很多。🔭

延伸閱讀
#

科技觀點 - 本文屬於一個選集。
§ 27: 本文

相關文章

MLX-LM 實戰:在 Apple Silicon 上跑本地模型推論
·9 分鐘· loading · loading
Mlx MLX-LM LLM Apple-Silicon Python Local AI
MLX-LM 模型轉換與量化:4/8-bit、Mixed Quant 與品質評測
·9 分鐘· loading · loading
Mlx MLX-LM LLM Quantization Apple-Silicon Model Conversion Local AI
MLX + Embeddings:在 Apple Silicon 上打造本地語意搜尋
·7 分鐘· loading · loading
Mlx Embeddings Semantic Search Apple-Silicon Python
MLX-LM 長文本聊天實戰:Context、KV Cache 與記憶體取捨
·10 分鐘· loading · loading
Mlx MLX-LM LLM Long Context KV Cache Apple-Silicon Local AI
MLX-LM LoRA 微調入門:Adapter、資料格式與 Apple Silicon 本機評測
·13 分鐘· loading · loading
Mlx MLX-LM LoRA Fine-Tuning Apple-Silicon Local AI
MLX-LM 批次推論實戰:Prompt Template、抽樣參數與本機評測流程
·9 分鐘· loading · loading
Mlx MLX-LM LLM Batch Inference Apple-Silicon Local AI