以图搜图总翻车?多模态 Embedding + Milvus 图文互检生产实战

MMT 0 次阅读
以图搜图总翻车?多模态 Embedding + Milvus 图文互检生产实战

电商拍照购物、版权溯源、设计素材库都在做「以图搜图」,但很多人直接用 CLIP 向量化 + 向量库检索后发现:召回不准、中文失效、索引一塌糊涂。本文从双塔对比学习原理讲起,用最新 SigLIP 多模态 Embedding + Milvus 从零搭建生产级图文互检系统,并给出 2026 年最新 CCKM Benchmark 实测选型数据。

开篇:从一个真实业务场景说起

假设你负责一个电商平台的「拍照购物」功能:用户拍一张运动鞋的照片,系统要在 1000 万级商品图库里瞬间找出同款或相似款。再比如你维护一个版权图库,需要检测某张图是否被裁剪、加滤镜后盗用。这些场景的核心都是同一个能力——跨模态检索(Cross-Modal Retrieval):让「图片找图片」「文字找图片」「图片找文字」在同一套系统里都能跑通。

很多人第一反应是:把图片丢给视觉大模型(VLM)生成一段描述,再走文本检索。这条路对单张图可行,但对千万级图库来说,每张图都过一次大模型推理的成本和延迟都不可接受。真正工业界的做法是多模态 Embedding(Multimodal Embedding):用一个双塔模型把图片和文本编码进同一个向量空间,离线批量向量化建索引,在线做 ANN(近似最近邻)检索,单次查询毫秒级返回。本文就围绕这条技术路线,从原理到生产完整讲透,全程代码可复现。

技术背景与核心概念扫盲

什么是多模态 Embedding

Embedding 是把非结构化数据(文本、图片、音视频)映射成固定维度的浮点向量。多模态 Embedding 的特殊之处在于:图片编码器和文本编码器共享同一个向量空间,语义相近的图文向量距离近。这样「一只白色的狗」这句话的向量,和一张白狗照片的向量,余弦相似度就很高——文搜图、图搜图在数学上统一成了「向量最近邻检索」。

双塔架构与对比学习

实现上述目标的标准范式是双塔架构(Two-Tower / Dual Encoder):一个图像编码器(Image Encoder)塔、一个文本编码器(Text Encoder)塔,各自独立编码,最后通过**对比学习(Contrastive Learning)**拉近正样本对、推远负样本对。下图是 CLIP 训练过程的经典示意:批量图文对输入两个编码器,输出特征矩阵对角线上是正样本,其余都是负样本。

CLIP 双塔对比学习训练流程示意:图像与文本分别编码后计算相似度矩阵,最大化对角线正样本相似度、最小化非对角线负样本相似度

这条路的代表作是 2021 年 1 月 OpenAI 发布的 CLIP(Contrastive Language-Image Pre-training),它在 4 亿图文对(WIT 数据集)上预训练,zero-shot 在 ImageNet 上达到 76.2% 精度,与当时有监督训练的 ResNet-50 打平。2023 年 Google DeepMind 发布 SigLIP(Sigmoid Loss for Language-Image Pre-training),用 sigmoid 损失替代 softmax 损失,训练更稳、对小 batch 更友好,成为新一代双塔基准。

适用边界:什么时候用它,什么时候不用

多模态 Embedding 适合「召回」:海量数据、高吞吐、低延迟、只求粗粒度相似。它不适合「细粒度理解」:比如判断「图里这个人是不是在喝咖啡」这种需要推理的问题,应该交给 VLM(如 Qwen2.5-VL)做生成式理解。生产系统中两者经常组合:Embedding 负责第一轮召回 Top-100,VLM 或重排序模型负责精排。想明白这条边界,你就不会拿着 CLIP 硬刚 VQA 了。

底层原理深度拆解

CLIP 的对比学习:softmax 归一化带来的三个隐患

CLIP 的训练目标是把 batch 内 N 个图文对变成 N×N 的相似度矩阵。矩阵对角线上是正样本对,其余 N²-N 个都是负样本对。损失函数是对称的交叉熵(InfoNCE 形式):

L = -1/|B| Σ_i log( exp(sim(I_i,T_i)) / Σ_j exp(sim(I_i,T_j)) )

公式里的分母是归一化项(normalization term),它把相似度变成概率。这个设计带来了三个工程隐患:

  1. batch 必须大:分母遍历了整个 batch 的负样本,负样本越丰富,学习信号越好,所以 CLIP 训练动辄上万甚至几十万的 batch size,普通团队根本跑不动;
  2. batch 内不能有相似样本:如果 batch 里恰好有两只柯基犬的照片,它们的相似度会污染分母,导致 loss 震荡,训练前还得做语义去重;
  3. loss 要算两遍:图像固定遍历文本 + 文本固定遍历图像,计算量翻倍。

SigLIP 的 sigmoid 损失:干掉归一化项

SigLIP 的思路非常直接:把多分类的 softmax 换成逐对独立的二分类 sigmoid。每个图文对独立计算损失,互不依赖:

L = -1/|B| Σ_i Σ_j log( 1 / (1 + exp( z_ij · (-t · x_i·y_j + b) )) )

其中 z_ij 表示这对样本是正样本(+1,对角线)还是负样本(-1,其余);t 是可学习温度(temperature),控制 sigmoid 的陡峭程度;b 是可学习偏置(bias),用于抵消训练初期正负样本不平衡。由于损失不再依赖 batch 内其他样本,batch 大小不再影响损失形式,小 batch 也能稳定训练。论文实测:batch size < 16k 时 sigmoid loss 显著优于 softmax;同样 4 张 TPU-v4 上,SigLIP 能塞下 4096 的 batch,而 CLIP 只能塞 2048。工程上这意味着:如果要自己微调图文检索模型,SigLIP 的训练成本门槛比 CLIP 低一个量级——这也是 PaliGemma 等新一代 VLM 换用 SigLIP 作为视觉塔的原因。

Modality Gap:跨模态检索的最大敌人

一个反直觉的现象是:即使训练完成,图片向量簇和文本向量簇在向量空间里往往并不重合,而是各自聚成两团、中间隔着一段距离,这段距离被称为 Modality Gap。gap 越大,图文互检越难——文本查询向量离目标图片簇越远,检索精度越差。下图直观展示了不同 gap 下图文向量的分布差异:gap 小(0.25)时两个簇重叠,跨模态匹配容易;gap 大(0.83)时两簇分离,匹配可靠性大幅下降。

Modality Gap 对比示意:左图为小 gap(0.25)图文簇重叠、跨模态匹配容易;右图为大 gap(0.83)图文簇分离、匹配可靠性差

2026 年 Zilliz 的 CCKM Benchmark 实测了各模型的 modality gap:Qwen3-VL-Embedding-2B 仅 0.25,Gemini Embedding 2 为 0.73,老牌 CLIP ViT-L-14 高达 0.83。这直接解释了为什么新版多模态模型在跨模态 R@1 上碾压 CLIP——不是特征提取变强了,而是模态对齐(alignment)做得更好

演进路线:从 CLIP 到 2026 年的多模态 Embedding

  • BLIP(2022):ViT+BERT 双塔,加入 ITC/ITM/LM 三个目标,用 CapFilt 清洗噪声图文对;
  • BLIP-2(2023):引入 Q-Former 桥接冻结的视觉编码器与冻结的 LLM,只训练中间层,129M 数据即可训练;
  • SigLIP2(2025):在 sigmoid 损失基础上融合自监督、解码器预训练、数据蒸馏等多项技术,支持密集定位任务;
  • Qwen3-VL-Embedding(2025):把 2B 的 VLM 改造为检索模型,modality gap 做到 0.25,跨模态 R@1 登顶;
  • Jina CLIP v2(2025):现代化的 CLIP 架构,原生多语言多模态;
  • Gemini Embedding 2(2025):闭源 API,文本/图片/视频/音频/PDF 全模态覆盖,跨语言检索接近满分。

可以看出,2026 年的多模态 Embedding 已分化为两条路线:开源轻量的(Qwen3-VL、SigLIP、Jina CLIP v2)和闭源全能的(Gemini Embedding 2、Voyage MM-3.5)。

手把手实战落地

下面我们搭建一个完整的图文互检系统:SigLIP 负责向量化,Milvus 负责存储与检索。环境假设 Python 3.10+,有 GPU 更好(无 GPU 也能跑,只是慢一些)。整体架构如下:

flowchart LR
    subgraph 离线索引[离线批量建索引]
    A[千万级图片库] --> B[SigLIP 图像编码器]
    B --> C[1152 维归一化向量]
    C --> D[(Milvus 集合<br/>HNSW 索引)]
    end
    subgraph 在线检索[在线实时检索]
    Q[文本查询 / 上传图片] --> E[对应编码塔]
    E --> F[查询向量]
    F --> D
    D --> G[Top-K 相似图片]
    end

第一步:安装依赖

# 向量数据库客户端 + 多模态模型 + 图像处理
pip install --upgrade pymilvus transformers pillow torch

Milvus 直接使用 Milvus Lite(单文件嵌入式版本),无需部署服务端,最适合本地开发和原型验证;生产环境换成 Docker/K8s 部署的 Milvus 2.6,连接方式完全一致。

第二步:加载模型与图文向量化

代码示例 1:加载 SigLIP 模型(含关键参数注释)

import torch
from transformers import AutoModel, AutoProcessor

# google/siglip-so400m-patch14-384:视觉塔为 SoViT-400m,输入分辨率 384x384
# 相比 CLIP ViT-L/14(224px),SigLIP 在图文检索上零样本 ImageNet 精度更高(83.1% vs 75.5%)
model_name = "google/siglip-so400m-patch14-384"
model = AutoModel.from_pretrained(model_name, torch_dtype=torch.float16)
processor = AutoProcessor.from_pretrained(model_name)
model.eval()  # 推理模式,关闭 dropout 等训练行为

# SigLIP 视觉塔 hidden_size 为 1152,文本塔同为 1152,两塔向量维度一致
print("向量维度:", model.config.vision_config.hidden_size)

代码示例 2:图文统一向量化函数(L2 归一化是命门)

from PIL import Image

def encode_image(image_path: str) -> list[float]:
    """将图片编码为归一化的 1152 维向量"""
    image = Image.open(image_path).convert("RGB")  # 统一转 RGB,避免 RGBA/灰度图报错
    inputs = processor(images=image, return_tensors="pt")
    with torch.no_grad():
        feat = model.get_image_features(**inputs)
    # L2 归一化:向量长度变为 1,之后余弦相似度 = 内积,可直接用 IP 度量
    feat = torch.nn.functional.normalize(feat, p=2, dim=-1)
    return feat.squeeze().tolist()

def encode_text(text: str) -> list[float]:
    """将文本编码为归一化向量,与图片向量位于同一空间"""
    inputs = processor(text=text, return_tensors="pt")
    with torch.no_grad():
        feat = model.get_text_features(**inputs)
    feat = torch.nn.functional.normalize(feat, p=2, dim=-1)
    return feat.squeeze().tolist()

⚠️ 归一化必须做:SigLIP/CLIP 训练时对特征做了 L2 归一化,推理时不归一化,相似度分数会失真,检索排序直接崩。

第三步:创建 Milvus 集合

代码示例 3:Milvus Lite 建集合 + HNSW 索引参数

from pymilvus import MilvusClient

# uri 指向本地文件即自动启用 Milvus Lite;生产环境改成 http://localhost:19530
client = MilvusClient(uri="multimodal_search.db")

COLLECTION = "image_gallery"
DIM = 1152  # 必须与模型输出维度一致,否则插入时报错

if client.has_collection(COLLECTION):
    client.drop_collection(COLLECTION)  # 演示场景先清库

client.create_collection(
    collection_name=COLLECTION,
    dimension=DIM,
    primary_field_name="id",      # 主键字段,auto_id 自动生成
    vector_field_name="vector",   # 向量字段名
    metric_type="COSINE",         # 相似度度量:归一化后用 COSINE 或 IP 均可
    auto_id=True,                 # 主键自增
    enable_dynamic_field=True,    # 开启动态字段:filepath 等标量字段无需预定义
    index_params={
        "index_type": "HNSW",     # 分层可导航小世界图索引,召回率/延迟均衡
        "M": 16,                  # 每个节点的最大连接数,越大召回越高、内存越大
        "efConstruction": 200,    # 建索引时的动态列表大小,越大索引质量越高
    },
)
print("集合创建完成")

第四步:批量灌库

代码示例 4:遍历图库批量向量化并写入

import os
from glob import glob

def build_index(image_dir: str, batch_size: int = 64):
    """批量将图片向量化后写入 Milvus,支持 jpg/png/webp"""
    paths = []
    for ext in ("*.jpg", "*.jpeg", "*.png", "*.webp"):
        paths += glob(os.path.join(image_dir, "**", ext), recursive=True)
    print(f"发现 {len(paths)} 张图片,开始灌库...")

    for i in range(0, len(paths), batch_size):
        batch = paths[i : i + batch_size]
        rows = []
        for p in batch:
            try:
                rows.append({"vector": encode_image(p), "filepath": p})
            except Exception as e:
                print(f"跳过损坏图片 {p}: {e}")  # 单张失败不影响整体
        if rows:
            client.insert(collection_name=COLLECTION, data=rows)
    print(f"灌库完成,当前集合行数: {client.get_collection_stats(COLLECTION)['row_count']}")

# build_index("./images_folder/train")  # 换成你自己的图库目录

第五步:文搜图 + 图搜图

代码示例 5:文搜图——用一句话找图片

def search_by_text(query: str, top_k: int = 5) -> list[tuple[str, float]]:
    """文本查询 → 向量 → Milvus 检索 → 返回 (图片路径, 相似度)"""
    q_vec = encode_text(query)
    hits = client.search(
        collection_name=COLLECTION,
        data=[q_vec],
        limit=top_k,
        output_fields=["filepath"],   # 需要返回的标量字段
    )
    return [(h["entity"]["filepath"], round(h["distance"], 4)) for h in hits[0]]

# 示例:查询 "a white dog"(英文效果最佳;中文建议用 SigLIP2 多语言版或 Chinese-CLIP)
for path, score in search_by_text("a white dog"):
    print(f"{score:.4f}  {path}")

代码示例 6:图搜图——上传一张图找相似图

def search_by_image(image_path: str, top_k: int = 5) -> list[tuple[str, float]]:
    """图片查询 → 向量 → Milvus 检索,实现以图搜图 / 相似图检测"""
    q_vec = encode_image(image_path)
    hits = client.search(
        collection_name=COLLECTION,
        data=[q_vec],
        limit=top_k,
        output_fields=["filepath"],
    )
    return [(h["entity"]["filepath"], round(h["distance"], 4)) for h in hits[0]]

# 示例:用图库里某张图查它的"近亲",可用于版权溯源/去重
for path, score in search_by_image("images_folder/train/n01440764/n01440764_10026.JPEG"):
    print(f"{score:.4f}  {path}")

到这里,一个可用的图文互检系统已经跑通。search_by_textsearch_by_image 底层是同一个 ANN 检索,区别只在查询向量来自哪个编码塔——这就是「图文不分家」的数学基础。

第六步:封装为生产级 HTTP 服务

代码示例 7:FastAPI 封装(含批量请求与错误兜底)

from fastapi import FastAPI, UploadFile, File
from pydantic import BaseModel

app = FastAPI(title="多模态图文检索服务")

class TextQuery(BaseModel):
    text: str
    top_k: int = 5

@app.post("/search/text")
def text_search(q: TextQuery):
    """POST /search/text  body: {"text": "a red car", "top_k": 10}"""
    results = [{"filepath": p, "score": s} for p, s in search_by_text(q.text, q.top_k)]
    return {"query": q.text, "results": results}

@app.post("/search/image")
def image_search(file: UploadFile = File(...)):
    """POST /search/image  multipart 上传图片文件"""
    tmp_path = f"/tmp/{file.filename}"
    with open(tmp_path, "wb") as f:
        f.write(file.file.read())          # 落盘后交给编码器
    results = [{"filepath": p, "score": s} for p, s in search_by_image(tmp_path)]
    return {"results": results}

启动服务:uvicorn main:app --host 0.0.0.0 --port 8000。生产环境中模型加载做成全局单例(如上),避免每个请求重复加载权重;并发推理用 torch.cuda.amp 或 batching 进一步压吞吐。

关键细节与踩坑指南

这一节全部来自真实项目里踩过的坑,按频率排序。

坑 1:中文查询效果差。 原版 CLIP 的 tokenizer 是英文 BPE,对中文几乎无感知,中文字符被拆成字节级 token,语义完全丢失。解决方案:① 用 Chinese-CLIP(阿里开源,中英双语图文对齐);② 用 SigLIP2 或 Qwen3-VL-Embedding 等多语言模型;③ 查询前先把中文翻译成英文(延迟高,不推荐做在线主路径)。

坑 2:忘记归一化导致相似度失真。 上面代码里 normalize 是命门。没归一化时,向量模长差异会主导内积结果,大图/长文本天然"更相似",排序彻底乱掉。

坑 3:维度不匹配报错。 Milvus 建集合时 dimension 必须精确等于模型输出维度。CLIP ViT-B/32 是 512,ViT-L/14 是 768,SigLIP SO400M 是 1152。换模型忘记重建集合,插入时直接抛异常。

坑 4:索引参数照抄默认值。 HNSW 三个参数互相关联:M 越大召回越高、内存和构建时间越大,一般 1632;efConstruction 控制建图质量,200400 常见;查询参数 ef 每次搜索可单独传,越大召回越高但延迟越高,线上从 64 起步调。

# 查询时动态调整 ef,兼顾召回与延迟
client.search(
    collection_name=COLLECTION,
    data=[q_vec],
    limit=10,
    search_params={"ef": 128},   # 高召回场景调大,低延迟场景调小
)

坑 5:一次性向量化所有图片导致 OOM。 千万级图库不能一张张过(太慢)也不能全量塞显存(会爆)。正确姿势是分批 + FP16 推理:

def encode_batch(image_paths: list[str], batch_size: int = 32) -> list[list[float]]:
    """分批向量化,规避显存溢出;fp16 可再省一半显存"""
    vecs = []
    for i in range(0, len(image_paths), batch_size):
        batch_imgs = [Image.open(p).convert("RGB") for p in image_paths[i:i+batch_size]]
        inputs = processor(images=batch_imgs, return_tensors="pt")
        with torch.no_grad():
            feats = model.get_image_features(**inputs)  # get_image_features 原生支持 batch
        vecs.extend(torch.nn.functional.normalize(feats, p=2, dim=-1).tolist())
    return vecs

坑 6:相似度度量选择。 归一化后 COSINE 和 IP 数学等价,但 COSINE 语义更直观(分数范围 [-1,1])。如果未来要上量化(如 int8),IP 配合归一化向量通常更稳。别用 L2 欧氏距离,它与余弦相似度不等价,会改变排序。

坑 7:图像预处理差异。 SigLIP 的 384 分辨率比 224 分辨率召回高但推理慢;processor 内置的 resize/归一化参数必须用它自带那套,自己用 cv2.resize 随手缩放会破坏分布。另外 convert("RGB") 必须加——灰度图、RGBA 图是生产数据里最常见的"隐形杀手"。

生产环境最佳实践

一套可直接复用的生产配置模板

# docker-compose.yml 片段:Milvus 2.6 Standalone 生产部署(最小高可用)
services:
  etcd:
    image: quay.io/coreos/etcd:v3.5.18
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
  minio:
    image: minio/minio:RELEASE.2024-01-16T16-07-38Z
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
  milvus:
    image: milvusdb/milvus:v2.6.5
    command: ["milvus", "run", "standalone"]
    ports:
      - "19530:19530"   # gRPC 客户端端口
# 生产连接配置:与 Milvus Lite 同一套 MilvusClient API,无缝切换
client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus",          # 生产务必改密码
)

双路召回 + 重排序,别裸奔单一向量

单一稠密向量的天花板很明显:CLIP 系模型对「颜色换一下」「构图细节不同」的商品图区分度不足。生产系统建议做混合检索(Hybrid Search):一路是 SigLIP/CLIP 稠密向量,另一路是图片关联文本的稀疏检索(BM25/Sparse),再用 RRF(Reciprocal Rank Fusion) 或重排序模型融合。Milvus 2.4+ 原生支持多向量字段,一个集合里同时存 dense 和 sparse 向量,一次查询并行召回再融合:

def reciprocal_rank_fusion(ranked_lists: list[list[str]], k: int = 60) -> list[tuple[str, float]]:
    """RRF 融合多路召回:按名次加权,不依赖分数绝对大小,抗分布漂移"""
    scores: dict[str, float] = {}
    for lst in ranked_lists:
        for rank, doc in enumerate(lst):
            scores[doc] = scores.get(doc, 0.0) + 1.0 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

监控与成本

  • 监控三件套:查询 P99 延迟(目标 <50ms)、召回率(抽样人工标注 Top-10 命中率)、QPS 与索引内存占用。Milvus 暴露 Prometheus metrics,接入 Grafana 即可。
  • 成本优化:① 向量降维——Jina v4 支持 MRL(Matryoshka Representation Learning) 截断到 256 维仍保持 95%+ 质量,存储直降 8 倍;② 量化——Milvus 支持 int8/FP16 标量量化,内存再减半;③ 冷热分层——老图放磁盘索引(DiskANN),热图放内存 HNSW。
  • 数据一致性:图片库增量更新用 Milvus 的 upsert 按主键覆盖,删除用 delete 按主键过滤,别全量重建。

横向对比与选型建议

模型 来源 参数 维度 模态 跨模态 R@1* Modality Gap 适合场景
CLIP ViT-L-14 OpenAI(2021) 428M 768 图文 0.768 0.83 老系统迁移/基线
SigLIP SO400M Google(2024) 400M 1152 图文 略优于CLIP 较小 自托管、中文弱
Jina CLIP v2 Jina(2025) ~1B 1024 多语言图文 0.873 0.87 多语言图文互检
Qwen3-VL-Embedding-2B 阿里(2025) 2B 2048 图文/视频 0.945 0.25 开源首选、跨模态召回
Gemini Embedding 2 Google(2025) 闭源 3072 文/图/视频/音频/PDF 0.928 0.73 全模态、跨语言最强
Voyage MM-3.5 Voyage(2026) 闭源 1024 图文/视频 0.900 0.59 均衡、MRL 友好

*跨模态 R@1 数据来自 Zilliz CCKM Benchmark(2026 年实测,200 图文对 + 600 hard negatives 干扰项)。

选型决策路径

  1. 数据只有英文、预算敏感、自托管 → SigLIP 或 CLIP 系,几百 M 参数单卡可跑;
  2. 中文/多语言、要开源可控 → Jina CLIP v2(1B,多语言)或 Qwen3-VL-Embedding-2B(跨模态精度最高但显存要求高);
  3. 图片+视频+音频+PDF 全模态、不差钱 → Gemini Embedding 2,跨语言检索 0.997 近乎满分;
  4. 存储成本敏感 → Voyage MM-3.5 或 Jina v4(支持 MRL 截断到 256 维);
  5. 只是做相似图去重/版权溯源 → 不需要文本塔,CLIP 单视觉塔即可,别为用不上的能力付费。

性能实测与效果验证

CCKM Benchmark:2026 年跨模态检索实测

Zilliz 在 2026 年发布的 CCKM Benchmark 用 200 个 COCO 图文对 + 600 个 hard negatives(只差一两个细节的干扰描述)测试了 10 款模型,跨模态检索 R@1 排名如下:

2026 CCKM Benchmark 跨模态检索 R@1 排名:Qwen3-VL-2B(0.945) > Gemini Embedding 2(0.928) > Voyage MM-3.5(0.900) > Jina CLIP v2(0.873) > CLIP ViT-L-14(0.768)

排名 模型 跨模态 R@1 Modality Gap
1 Qwen3-VL-Embedding-2B 0.945 0.25
2 Gemini Embedding 2 0.928 0.73
3 Voyage Multimodal 3.5 0.900 0.59
4 Jina CLIP v2 0.873 0.87
5 CLIP ViT-L-14(基线) 0.768 0.83

关键结论:开源 2B 的 Qwen3-VL 反超所有闭源 API,modality gap(0.25)只有 Gemini 的三分之一、CLIP 的三成——2026 年做跨模态检索,开源已经不是妥协方案,而是首选方案。这也解释了为什么很多团队把老 CLIP 换掉后,图文召回率能提升 20 个百分点以上。

零样本 ImageNet 精度(OpenCLIP 模型库,2026 实测)

模型 训练数据 参数量 Zero-shot ImageNet
ViT-L-14-quickgelu (CLIP) WIT 13B 75.5%
ViT-SO400M-14-SigLIP-384 WebLI 45B 83.1%
ViT-L-14 (DFN) DFN-2B 39B 82.2%
ViT-H-14-378-quickgelu (DFN) DFN-5B 44B 84.4%

同样 224 输入,SigLIP 比 CLIP 高 6.5 个百分点;把输入分辨率提到 384 还能再涨。分辨率是免费午餐:推理资源够的话,优先选 384 及以上输入版本的模型。

效果验证:文搜图实测样例

Milvus 官方教程用 CLIP ViT-B/32 在 ImageNet 子集(100 类 × 10 张)上做文搜图,查询 "a white dog" 时 Top-10 全部命中白狗图片,单次检索毫秒级返回:

Milvus 文搜图实测效果:查询 "a white dog" 返回 Top-10 全部为白狗图片,验证图文向量检索端到端可用

总结与未来展望

多模态 Embedding 双塔架构把「以图搜图、文搜图、图搜文」统一成了向量最近邻检索:CLIP 用 softmax 对比损失奠定范式,SigLIP 用 sigmoid 损失解决训练稳定性和 batch 依赖,2025-2026 年的新一代模型(Qwen3-VL、Jina v2、Gemini Embedding 2)则通过更小的 modality gap 把跨模态召回精度推上新高。生产落地记住四件事:归一化必须做、维度必须对齐、索引参数按量调、双路召回 + RRF 别裸奔。未来演进方向很明确:MRL 降维与量化让存储成本持续走低、late-interaction(ColPali 式)弥补全局向量的细粒度短板、多模态 Embedding 与 VLM 生成式理解的前后端融合(召回 + 精排 + 推理)将成为标准检索流水线。

延伸阅读