一. 前言:讓模型看圖,不只是多傳一個檔案 #
文字模型很會回答問題,但你把一張截圖放在旁邊,它其實什麼都沒看到。
要讓本地模型理解照片、圖表、介面截圖或掃描文件,需要的是 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("這張圖是什麼?"),而是:
- 載入模型與 processor
- 用 chat template 格式化問題
- 告訴 template 有幾張圖片
- 同時把 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.jpg、02-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 時至少檢查:
- 是否真的支援圖片,而不是純文字模型
- 是否支援多圖、影片或你需要的語言
- 權重格式是否可由目前
mlx-vlm載入 - 記憶體是否適合自己的 Mac
- 模型授權是否允許預定用途
- 模型卡是否說明建議 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_include、must_not_claim 與 manual_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,以及自己的小型評測集。
先從一張圖、一個具體問題開始。確認模型真的看對,再擴充到多圖、批次與多輪互動。這樣做慢一點點,會比得到一大堆漂亮幻覺省事很多。🔭