跳到主要內容

【2026 最新】WeMM-Embedding 多模態搜尋教學:用文字找照片、影片與掃描文件

最後更新: ·
WeMM-Embedding 多模態搜尋教學首圖,四種媒體軌道進入共享座標系並以放大鏡尋找最近鄰

你記得某張照片裡有「狗在海邊奔跑」,也記得某支影片出現過同一個場景,卻完全不記得檔名是 IMG_4837VID_202608,還是哪一層資料夾。傳統搜尋看不到畫面語意;先替每張圖寫 caption 又慢,而且描述沒寫到的細節會直接消失。

WeMM-Embedding 多模態搜尋要解的就是這個問題:它把文字、照片、影片與視覺文件轉成可以互相比較的向量。你輸入一句「有折線圖與紅色警告框的頁面」,系統不是比對檔名,而是在同一個向量空間裡找最近的內容。

先記住全文的核心句:多模態搜尋=把不同媒體放進同一張座標地圖,再找離查詢最近的鄰居。這篇會從這張地圖出發,完成 2B 模型、FAISS 索引、三種跨模態查詢與 12 題小型 Eval;不把官方 benchmark 當成你的資料夾成績,也不把「2B」誤寫成一般電腦必然跑得動。

TL;DR:先看懂這 6 件事

  • Embedding 不是 caption:它輸出的是向量,不是一段圖片描述。
  • WeMM-Embedding 目前有 2B、4B、9B 三個官方版本;本文用 2B 建立最小流程。
  • 照片、影片與掃描頁都要用同一個模型版本、同一個維度,並做 L2 正規化。
  • 正規化後可用 FAISS IndexFlatIP 做精確 cosine 最近鄰搜尋。
  • 256 維是值得先測的索引起點,但不會縮小模型,也不保證你的掃描文件品質不降。
  • 先準備 12 題私有 Eval,再比較 Hit@k、延遲、記憶體與錯誤類型;不要用肉眼挑兩個成功案例。

WeMM-Embedding 是什麼?先別把它想成圖片描述器

一般文字 embedding 會把句子轉成一串數字,語意相近的句子在向量空間裡靠得比較近。多模態 embedding 再往前一步:文字、圖片、影片與頁面截圖都進入同一個可比較空間。因此「一隻狗在沙灘跑」的文字向量,能靠近真正呈現這個畫面的照片或影片向量。

文字、照片、影片與掃描頁經同一模型轉成正規化向量,再由 FAISS 找最近鄰的示意圖
多模態搜尋的關鍵不是先替所有媒體寫文字,而是讓不同輸入共享同一套座標。

它和 caption、OCR、RAG 差在哪裡?

  • Caption:把圖片變成文字。好處是可讀,但描述沒提到的顏色、位置、版面或小物件可能遺失。
  • OCR:抽出畫面上的文字。它很適合發票、表格與掃描件,卻不等於理解整張頁面的視覺語意。
  • Text-only embedding:搜尋檔名、OCR、人工標籤或 caption;輸入仍然先被壓成文字。
  • Multimodal embedding:直接把媒體變成向量,能做文字找圖片,也能拿圖片或影片當查詢。
  • RAG:是「先檢索,再把證據交給生成模型回答」的完整流程;embedding 只是其中一種檢索零件。若你還不熟這個邊界,可先讀 RAG 是什麼

兩種方法不是互斥。掃描文件常常值得同時保留 OCR 文字與頁面影像:前者抓精確字詞,後者保留表格、圖形與版面。之後再用 hybrid search 合併排名;做法可接著參考 BM25、Embedding 與 Hybrid Search 的比較

2026 年 8 月版本快照:2B、4B、9B 怎麼選?

截至 2026 年 8 月 27 日,Tencent 官方 repository列出三個版本:2B 的完整向量是 2,048 維,4B 是 2,560 維,9B 是 4,096 維。三者都支援 Matryoshka 維度;2B 可選 64、128、256、512、1,024、2,048 維。

本文固定使用 WeMM-Embedding-2B,因為目標是先驗證檢索方法,不是預設大模型一定更好。官方 2B safetensors 檔約 5.44 GB;這是下載檔大小,不是執行時 VRAM 數字。官方範例走 CUDA 與 BF16;截至 2026 年 8 月 27 日,官方 README 與 2B model card 都未列一般推論的最低 GPU/VRAM。應先用自己的素材量測,不能從「2B」推導出「筆電一定能跑」。

另一個邊界很清楚:官方 README 目前明確寫著不支援 audio input。若影片的關鍵只存在於人聲,可先另外做 ASR,把逐字稿當文字欄位;那是你加入的第二條訊號,不是模型直接聽懂音訊。

Step 1:建立可重現環境,版本不要跟著 HEAD 漂移

這個模型需要 trust_remote_code=True,代表載入 Hugging Face 模型時會執行 repository 內的 Python。依 Hugging Face 官方安全建議,做正式專案時應先檢查程式碼,再把 revision 固定到你已審過的 commit。本文鎖定 2B revision df8094e5caf29083d9cac28e96fad6cfbe3ee57f,並依官方 repository 固定前處理版本。

conda create -n wemm-search python=3.11 -y
conda activate wemm-search

pip install torch transformers==5.2.0 "qwen-vl-utils[decord]==0.0.14" sentence-transformers==5.7.0 "accelerate>=1.1.0" numpy

conda install -c pytorch -c conda-forge faiss-cpu poppler

官方特別建議 transformers==5.2.0,因為更新版可能改變前處理行為。這不是單純「程式能不能 import」的問題:tokenization、影片取幀與多模態輸入位置一變,同一個檔案的向量也可能漂移。模型、套件、維度任一項改變,都應建立新索引並重跑 Eval。

如果你沒有相符的 CUDA 環境,最穩妥的第一步是用可控的 NVIDIA GPU 環境驗證 10 個檔案,而不是先下載完整相簿。CPU、Apple MPS、量化與不同 serving 後端的行為不在官方這組最小範例裡;若要走那些路徑,請把它當成另一個待驗證部署版本。

Step 2:整理照片、影片與掃描文件

先做一個小資料夾,不要直接索引十萬個檔案。建議放 12~30 個候選,刻意加入「看起來很像但答案錯」的 hard negatives,例如同一隻狗在室內、同一張表格但季度不同、同一場演講但片段不同。

media/
├── photos/
│   ├── dog-beach.jpg
│   └── dog-living-room.jpg
├── videos/
│   ├── beach-run.mp4
│   └── indoor-run.mp4
└── pages/
    ├── report-p01.png
    └── report-p02.png

掃描 PDF 要先轉成頁面圖片

官方介面接受的是 image/video/text 等輸入;「visual document」在這個實作裡應理解為頁面影像,不要把原始 PDF 路徑直接塞進 image 欄位。可先用 Poppler 將每頁轉成 PNG:

mkdir -p media/pages
pdftoppm -png -r 160 report.pdf media/pages/report

轉圖後保留 source_pdfpage_number 與輸出路徑,搜尋結果才有辦法回到正確頁碼。長影片也不宜只存一個全片向量:依場景或時間切段,metadata 記下 start_secend_sec,否則短暫事件很容易被整段主題稀釋。

Step 3:產生 embeddings,建立最小 FAISS 索引

從掃描媒體資料夾、PDF 轉圖、產生 256 維向量到 FAISS 查詢的七步流程圖
索引與查詢必須一路鎖住同一個模型 revision、維度與正規化方法。

下面是可以看懂整個機制的最小版。先把三個範例路徑換成你的檔案;正式批次可再改成遞迴掃描資料夾。程式刻意保留兩次正規化:模型已要求輸出 normalized embeddings,FAISS 前再做一次,讓 cosine 的前提明寫在程式裡。

from pathlib import Path
import json
import faiss
import numpy as np
from sentence_transformers import SentenceTransformer

MODEL_ID = "tencent/WeMM-Embedding-2B"
REVISION = "df8094e5caf29083d9cac28e96fad6cfbe3ee57f"
DIM = 256

items = [
    {"path": "media/photos/dog-beach.jpg", "kind": "image"},
    {"path": "media/videos/beach-run.mp4", "kind": "video"},
    {"path": "media/pages/report-p01.png", "kind": "image"},
]

def payload(item):
    key = "image" if item["kind"] == "image" else "video"
    return {key: str(Path(item["path"]).resolve())}

model = SentenceTransformer(
    MODEL_ID,
    revision=REVISION,
    trust_remote_code=True,
    device="cuda",
)

vectors = model.encode(
    [payload(item) for item in items],
    batch_size=1,
    truncate_dim=DIM,
    normalize_embeddings=True,
    convert_to_numpy=True,
)
vectors = np.ascontiguousarray(vectors, dtype="float32")
faiss.normalize_L2(vectors)

index = faiss.IndexFlatIP(DIM)
index.add(vectors)
faiss.write_index(index, "media.faiss")

with open("media.json", "w", encoding="utf-8") as f:
    json.dump(
        {"model": MODEL_ID, "revision": REVISION,
         "dimension": DIM, "items": items},
        f, ensure_ascii=False, indent=2,
    )

為什麼是 IndexFlatIP?

FAISS 官方 metric 說明指出:要做 cosine similarity,可先把資料向量與查詢向量 L2 normalize,再用 inner product。IndexFlatIP是不需要訓練、會逐一精確比較的 baseline,適合先確認流程與正確率;它不是大型 production 索引的自動答案。資料量變大後,IVF、HNSW 或其他近似索引還要另外測 recall、記憶體與延遲。

FAISS 要求固定維度的 contiguous float32 matrix。若今天用 256 維建索引,明天改成 512 維,不能把新向量混加進舊 index。截至 2026 年 8 月 27 日,官方 README 與三份 model card 只分別示範各 checkpoint,未提供跨模型 embedding-space 相容契約;因此換 4B 或 9B 時應建立獨立索引。

Step 4:完成文字找照片、圖片找影片與文件找畫面

查詢 A:文字找照片或影片

query = "一隻狗在海邊奔跑"
q = model.encode(
    [query],
    truncate_dim=DIM,
    normalize_embeddings=True,
    convert_to_numpy=True,
)
q = np.ascontiguousarray(q, dtype="float32")
faiss.normalize_L2(q)

scores, rows = index.search(q, k=3)
for rank, (score, row) in enumerate(zip(scores[0], rows[0]), 1):
    print(rank, float(score), items[int(row)])

分數只適合在同一個模型、revision、維度與前處理下相對比較。不要把 0.72 當成跨專案通用的「72% 正確」。真正的門檻要從你的正例、難負例與錯誤成本校準。

查詢 B:圖片找相關影片

image_query = {
    "image": str(Path("query/reference.jpg").resolve())
}
q = model.encode(
    [image_query],
    truncate_dim=DIM,
    normalize_embeddings=True,
    convert_to_numpy=True,
)
q = np.ascontiguousarray(q, dtype="float32")
faiss.normalize_L2(q)
scores, rows = index.search(q, k=5)

同一個 index 可以回傳照片、影片或掃描頁,所以結果頁要顯示 kind,不要只顯示分數。若圖片本身也在 corpus 裡,評測時要排除它,否則「找到自己」會把 Hit@1 灌高。

查詢 C:文件頁找相關畫面

把 PDF 頁面轉成 PNG 後,它就是 image query。你可以用一頁含折線圖的報告,去找另一份簡報中的相似圖表、影片裡的投影片畫面或同系列掃描頁。這種查詢最容易被低解析度、小字、版面相近但數值不同的 hard negative 欺騙,所以必須另外看 visual-document 題組,不能只看照片成功率。

Step 5:設計 12 題 Eval,和 text-only baseline 公平比較

四種跨模態搜尋各三題的十二題 Eval 設計與 Hit@k 延遲記憶體錯誤標籤
12 題只能當早期診斷;每組三題中少一題,組內結果就會改變 33.3 個百分點。

先寫答案,再跑查詢。每一題至少記錄:query、允許的正解 ID、最難的負例、預期模態、Top 5 結果與錯誤標籤。四組各三題:

  1. 文字找照片:物件與動作、屬性與位置、畫面文字各一題。
  2. 文字找影片:整段主題、短暫事件、相似但時序錯誤各一題。
  3. 圖片找影片:同場景、同物件、視覺相近 hard negative 各一題。
  4. 文件頁找畫面:相近表格、版面加語意、低畫質掃描各一題。

三條 baseline 要吃同一批候選

  • Filename baseline:只用檔名、資料夾名與既有標籤。
  • Text-only baseline:把 OCR、人工 caption 或既有 metadata 串成文字,再送入一個固定 revision 的文字 embedding 模型。
  • WeMM multimodal:直接輸入圖片、影片或頁面影像。

三條路徑必須搜尋同一批候選、使用同一組正解與相同的 k。若 text-only 路徑有人手寫了非常完整的 caption,而多模態路徑只有原圖,這仍然是有效的產品比較,但要把「人工標註成本」一起記錄,不能只報 Hit@k。

至少記錄 4 類指標

  • Hit@1/Hit@3:前 1 或前 3 名是否包含任一預先指定正解。
  • 延遲:把解碼/前處理、encoder、FAISS search 與 total 分開;cold start 另列。先 warm up,再報中位數與 P95。
  • 記憶體:分開記錄模型常駐、單次查詢 peak、CPU RSS 與 index bytes,別把 256 維降低的索引空間誤寫成模型變小。
  • 錯誤類型:語意錯、屬性/位置錯、OCR 錯、短暫事件錯、時序錯、hard negative 混淆、維度/版本錯或 OOM。

12 題很適合抓流程 bug,卻不適合宣布模型排名:總體少命中一題就會變動 8.3 個百分點,單一三題組更會變動 33.3 個百分點。若要做上線決策,逐步擴成更大的 held-out set,並保留每種模態與失敗成本的分層結果。你也可以把這套方法接到 12 題私有 Eval 的設計原則,但不要沿用那篇的生成式模型評分題目。

256 維真的夠嗎?先分清楚「索引變小」與「模型變小」

Tencent 技術報告針對 2B 模型表示:在 MMEB-v2 上,256 維保留完整 2,048 維之 image 與 video 平均表現的 98.7%。這是特定 benchmark 的相對保留率,不是「品質完全相同」,也不是你相簿的保證。報告同時指出 visual-document tasks 對降維更敏感。

Matryoshka 做的是取完整向量前 d 個座標,再重新 L2 normalize。它能減少每筆向量的儲存量與相似度計算量,但 encoder 還是同一個 2B 模型,模型檔、forward pass 與主要權重記憶體不會因為輸出 256 維就按比例縮小。

  • 先以 256 維建立最小 baseline。
  • 在完全相同的 12 題上重建 512 與 2,048 維 index。
  • 若照片/影片持平、掃描頁下降,分模態使用不同索引也是合理設計。
  • 只有當實際 Eval 證明 4B/9B 的增益值得額外成本,才升級 encoder;不要只看 family size。

官方 benchmark 怎麼讀,才不會把排行榜當產品保證?

官方報告在 MMEB-v2 的 78 個資料集上,列出 2B/4B/9B 平均分別為 77.9、79.2、80.6;image/video 使用 Hit@1,visual documents 使用 NDCG@5。這些數字是 Tencent 報告的 leaderboard snapshot,狀態日期為 2026 年 8 月 24 日,不是 AlphaLab 的獨立重現,也沒有告訴你家中照片的 Hit@3。

更重要的是,平均分會掩蓋方向差異:9B 的總平均較高,不代表每個模態、每個資料集都勝出;2B 與某些大型 baseline 的平均差距也可能非常小。報告裡的內部 26 題組與 14 次線上 A/B test 缺少公開樣本、流量與 effect size,只能理解為團隊的部署經驗陳述,不能當成可獨立核對的外部證據。

正確用法是把 benchmark 當成「值得進入你的候選名單」,再用私有 Eval 決定是否上線。企業若要進一步接權限、資料更新與生成回答,可參考 企業 AI 知識庫的分層架構;不要把本篇的單機 Flat index 直接當成完整知識庫。

8 個最常見的失敗點

  1. 偷偷升級套件:前處理變了卻沿用舊 index。解法是鎖 revision 與 requirements,任何改動都重建並重測。
  2. 把 inner product 直接叫 cosine:只有資料與 query 都 L2 normalize 時才成立。
  3. 混用維度:256 維 query 不能查 512 維 index;切換維度就建立新 index。
  4. 長影片只做一個向量:短暫事件被整體主題淹沒。先切段並保留時間碼。
  5. 把 PDF 當原生輸入:先轉成頁面影像,必要時另走 OCR 文字索引。
  6. 把相似度當機率:分數門檻只能在固定資料與錯誤成本下校準。
  7. 只測容易正例:沒有 hard negatives,Hit@1 很容易看起來完美。
  8. 把 Flat index 當 production 架構:資料變大後還要處理近似索引 recall、更新、刪除、權限、觀測與備援。

若要把這條檢索管線交給 AI Agent 呼叫,還要再補工具 schema、權限、超時、重試與停止條件;先讀 AI Agent Harness 是什麼,再接著看 最小 Harness 實作,會比直接把搜尋函式暴露給模型安全得多。

常見問題 FAQ

1. WeMM-Embedding 會先替圖片生成 caption 嗎?

這個檢索流程直接取得專用 <embedding> 位置的向量並做 L2 normalization,不需要先把圖片改寫成可讀 caption。你仍可另外保存 caption,作為 text-only baseline 或可解釋欄位。

2. 可以直接丟 PDF 嗎?

本文依官方公開介面把視覺文件當頁面影像處理。先將 PDF 每頁轉成 PNG/JPEG,再保存原檔與頁碼;不要把 PDF 路徑冒充 image path。

3. 可以搜尋影片裡的聲音嗎?

官方目前明確寫明不支援 audio input。可另做 ASR,把逐字稿加入 text-only 或 hybrid 路徑,再用你自己的 Eval 測試影片畫面與逐字稿該如何融合。

4. 256 維會讓 2B 模型下載變小嗎?

不會。它縮短輸出向量,主要節省 index storage 與 similarity search 工作;encoder 權重仍是同一個 checkpoint。

5. 為什麼 normalize 兩次?

官方實作會正規化完整向量;Matryoshka 截斷後還要再正規化。本文在進 FAISS 前顯式呼叫一次,是為了鎖住 cosine 前提,並防止後續換資料管線時漏掉這一步。

6. 可以把 2B 索引換成 9B query 嗎?

不要這樣做。截至 2026 年 8 月 27 日,官方 README 與 model cards 未提供跨模型 embedding-space 相容契約,而且完整維度也不同。換模型就重建 corpus 與 query,並使用獨立 index。

7. IndexFlatIP 可以放十萬張照片嗎?

它會精確掃過所有向量,能不能接受取決於維度、硬體、查詢量與延遲目標。先量測真實規模;若超標,再比較近似索引的 recall/latency trade-off,不能只用檔案數直接下結論。

8. 這篇代表 AlphaLab 已跑出 WeMM 的成績嗎?

不是。這是一套依官方程式與 FAISS 契約整理的可執行教學;本文只引用已標明出處的 Tencent 報告數字。12 題區塊提供的是評測設計,沒有填入未執行的 AlphaLab 成績。

重點整理

  • 真正的跨模態搜尋不是「先 caption 再搜尋」,而是把不同媒體放進同一個向量空間。
  • 先用 2B、256 維與 IndexFlatIP 建立精確 baseline,再用私有 Eval 決定是否升維、升模型或換近似索引。
  • 模型 revision、前處理版本、向量維度與 L2 normalization 是同一份索引契約,少鎖一項就可能產生靜默漂移。
  • 掃描 PDF 先轉頁面圖;長影片先切段;audio 另走 ASR/文字訊號。
  • 官方 benchmark 只能把模型送進候選清單,不能替代你自己的正例、hard negatives 與錯誤成本。

結論:先畫好自己的座標地圖,再談模型大小

回到開頭那張地圖:文字、照片、影片與掃描頁只有在同一個模型版本、同一維度、同一正規化規則下,距離才有意義。FAISS 負責快速找鄰居,卻不會替你定義什麼叫正確;那要靠預先寫好的正解、hard negatives 與逐步擴大的 Eval。

最好的起點不是「把整顆硬碟丟進去」,而是選 12~30 個刻意設計的媒體、跑 12 題、打開每一個 miss。當你知道錯在 OCR、時序、低畫質、降維還是版本漂移,才知道下一筆預算該花在 caption、ASR、較高維度、較大模型,還是更好的資料切分。

想把這套方法延伸成完整 AI 應用,可到 AlphaLab 線上課程AI 專區接著學習。

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週最多三封:一封 Weekly 週報與最多兩封關鍵 Alpha Signal。

我們不會 spam,隨時可退訂。