
搜索相关性度量这件事看起来简单实际很麻烦。难点不在“文本匹配”而在于 Query 和网页之间的相关性判断往往要“看得见图”才能做对。一个典型例子用户搜“白色帆布鞋 男款”商品页标题写的是“新款休闲鞋”正文没有任何“白色”“帆布”字样但首图就是一双白色帆布鞋。纯文本模型大概率漏判人类标注员扫一眼图就知道相关。这正是视觉语言模型Vision-Language ModelVLM进入相关性度量的核心动机把图片、版式、视觉语义一起纳入打分而不是只靠文字。这篇不是某个一键启动的整合包教程而是梳理 VLM 做 Web 规模搜索相关性度量这条技术路线它解决了什么、工程上要拆成几个阶段、模型怎么选、服务怎么搭、批量评估怎么做、显存和延迟成本大概是什么量级。文章会给出可落地的 API 服务模板、批量任务脚本和排查清单。适合做搜索、推荐、广告相关性评估的工程师以及准备把多模态模型接入线上打分链路的同学。先给结论。这套方案的核心特点可以概括为文本信号做兜底对比式 VLM 做召回和粗排生成式多模态大模型做精排和理由生成支持批量离线评估也可以封装成 REST API 接进自己的评估平台对硬件有明确要求但不夸张8GB 显存能跑对比式模型7B 级别多模态大模型建议 16GB 显存以上。下面的章节会围绕这些点逐个展开。1. 核心能力速览先把这条技术路线的主要参数列出来。以下硬件建议属于通用工程经验不是某个模型的官方最低要求实际以你选用的模型卡和量化方式为准。能力项说明技术路线对比式 VLM 做召回/粗排 生成式多模态大模型做精排/解释核心任务Query-Document 相关性打分、Query-Image 相关性判断、相关性二分类与理由生成输入数据查询文本、网页标题、网页正文、网页首图/商品图/网页截图可选 OCR 文本输出数据相关性分数0-1 或 1-5、相关性标签、判定理由推荐硬件独立显卡8GB 显存起7B 级别多模态大模型建议 16GB 以上支持平台Linux 优先Windows 可用于调试生产环境不建议启动方式Python 服务FastAPI 离线批处理脚本 评估指标计算API 能力可封装 REST 接口支持单条打分和批量打分批量任务支持建议配合任务队列、断点续跑和失败重试主要场景Web 搜索、电商搜索、广告相关性、图文检索效果评估为什么要分“对比式”和“生成式”两类 VLM因为 Web 规模搜索的打分链路对延迟和成本极其敏感。对比式 VLM 比如 CLIP、SigLIP 这类把文本和图片编码到同一向量空间算余弦相似度速度快、显存占用低适合在第一阶段从海量候选中筛出 top-K。生成式多模态大模型比如 Qwen-VL、InternVL 这类能同时看图和文字输出带理由的相关性判定质量高但推理慢、成本高只适合对 top-K 做精排或者离线构建评估集时使用。两条路线不是替代关系而是流水线上下游。2. 为什么相关性度量需要 VLM2.1 文本模型的天花板很明显传统相关性度量工具链大致经历了三个阶段BM25、TF-IDF 这类词法匹配到 Sentence-BERT 这类稠密向量检索再到 BERT/RoBERTa 这类 Cross-Encoder 精排。它们对纯文本语料有效但对真实网页有一个共同缺陷看不见图片。网页相关性判断里图片信息的占比比很多人想象的高。电商场景中标题可能刻意堆关键词或不堆关键词正文摘要经常是模板生成真正决定用户是否满意的是首图能不能清楚展示商品的款式、颜色、角度。新闻场景中配图能帮助确认事件主体和场景。本地生活场景中店铺头图直接决定了“这家店卖什么”。这些信号全部落在图片里文本模型面对的是信息残缺的输入。2.2 Web 页面的多模态特征必须利用一个真实的网页至少包含四类可被相关性模型利用的信号标题文本、正文文本、页面图片或截图、以及图片的 alt 文本和周边文字。传统做法是只取前两类后两类要么丢弃要么用 OCR 转成文本后再进模型。OCR 会丢信息比如颜色、形状、构图、物体相对位置这些视觉语义很难用文字完整还原。VLM 直接把图像像素作为输入在模型内部完成视觉编码和文本推理。它不需要你先把图片“翻译”成文字而是让模型自己判断“这张图里有什么、和 Query 是否匹配”。这是本质区别。2.3 VLM 带来的三个具体能力第一图文统一表征。对比式 VLM 把 Query 文本和图片投到同一个向量空间可以直接算相似度这为召回阶段提供了可用的多模态检索信号。第二细粒度视觉推理。生成式多模态大模型能处理“左图是红色款右图是黑色款Query 要红色款所以右图不相关”这类需要视觉细读的样本。第三可解释性。生成式模型可以输出判定理由这在构建评估集、审核错误样本、向业务方解释打分结果时非常有用。2.4 适用场景与边界适合的场景包括Web 搜索相关性评估、电商搜索、广告创意与 Query 相关性、图文检索的质量衡量、内容平台搜索的多样性评估。不适合的场景也要说清楚如果语料本身就是纯文本比如代码仓库、论文库、合同库VLM 带来的收益有限反而增加成本和延迟如果图片质量极差、大量缺图VLM 的优势也发挥不出来。另外任何涉及图像数据的使用都必须确认数据来源合法网页图片通常有版权评估数据集的构建需要遵守来源网站的授权条款和 robots 协议。3. 技术路线与数据流设计3.1 两阶段架构召回 精排Web 规模搜索面临的核心约束是数据量级。线上索引可能有数十亿网页不可能对每个候选都跑一次生成式多模态大模型。因此工程上必须拆成两阶段。第一阶段是召回和粗排。用对比式 VLM 为网页图片生成 embedding离线存入向量索引线上拿到 Query 后用同一个模型编码 Query 文本在向量索引里做 ANN 检索取出 top-K 候选。如果网页有正文文本也可以叠加文本向量召回做分数融合。这一阶段的目标不是精确而是用很低成本把相关候选捞回来。第二阶段是精排。对 top-K 候选把 Query、标题、正文摘要、页面图片或截图一起送入生成式多模态大模型让模型输出相关性分数和理由再按分数重排。这一阶段只处理几十到几百个候选成本可控质量和可解释性都有保障。3.2 相关性信号设计具体打分时可以拆成几个可独立验证的信号最后再融合S1 文本语义相似度Query 与标题、正文、alt 文本之间的文本相似度用文本 embedding 模型或 VLM 的文本编码器计算。S2 图文匹配度Query 与页面首图之间的图文相似度用对比式 VLM 的图文匹配头计算。S3 生成式判定把 Query、标题、正文、图片整体交给多模态大模型输出综合相关性判断。这个信号最贵也最接近人类标注员的行为。融合方式可以是加权求和也可以是级联阈值。一个稳妥的做法是先用 S1 和 S2 过滤掉明显不相关的候选再对边界样本调 S3这样既控制成本又保证精度。3.3 模型选型参考对比式 VLM 可以选 OpenAI 开源的 CLIP 系列、open_clip 复现的多种权重或者 SigLIP。如果业务以中文为主可以找中文语料训练的 CLIP 变体这类模型在中文图文检索任务上通常比英文原版更稳。生成式多模态大模型可以选 Qwen-VL、InternVL 等支持图片输入的开源模型具体用什么规模和量化取决于你的显存和延迟预算。选型时要同时看三个指标相关性判别能力、推理延迟、显存占用。不要只看刷分结果实际业务里延迟和成本往往比几个点的准确率提升更重要。3.4 标注集与评估指标做相关性度量必须先有一套人工标注的评估集。标注规范要写清楚什么算相关什么算部分相关什么算不相关边界情况怎么处理。评估指标以 NDCGk 和 MRR 为主配合人工标注与模型打分的一致性分析。每批模型迭代都要在固定评估集上重跑避免个别 prompt 修改后“顾此失彼”。4. 环境准备与前置条件4.1 硬件与系统建议使用 Linux 服务器原因不是 Windows 跑不了而是生产环境的进程管理、GPU 驱动、多卡推理和 Docker 部署都更省事。显卡方面跑对比式 VLM 的 base 规模模型8GB 显存基本够用跑 7B 级别生成式多模态大模型建议 16GB 以上开启 FP16 或 INT8 量化后更稳。磁盘至少要预留模型文件、评估数据集、输出结果三部分空间7B 模型权重大概 14GB 到 15GB量化后更小。4.2 Python 环境与依赖推荐 Python 3.10 以上用 conda 或 venv 隔离环境。基础依赖是 PyTorch、transformers、PIL、FastAPI、uvicorn、pydantic。如果做向量检索还需要一个 ANN 库比如 faiss 或 hnswlib。下面给一套通用的环境创建命令版本号请按你本机情况调整。conda create -n vlm-search python3.10 -y conda activate vlm-search # PyTorch 安装命令请参考 pytorch.org 的 CUDA 版本号 pip install torch torchvision pip install transformers pillow fastapi uvicorn pydantic pip install faiss-cpu httpx如果你的 GPU 驱动和 CUDA 版本不确定先用nvidia-smi查看驱动支持的 CUDA 版本再选择对应的 PyTorch 版本避免装完跑不起来。4.3 数据目录规划建议把输入、模型、输出分成三个目录避免一批任务跑完目录乱掉。下面是一个参考结构data/ queries.jsonl # 查询文本 docs/ # 网页正文或摘要 images/ # 页面图片或截图 labels.tsv # 人工标注结果 models/ clip-model/ # 对比式 VLM 权重 vlm-7b/ # 生成式多模态大模型权重 outputs/ batch-run-20250101/ # 每天/每次任务独立输出目录模型文件、输入素材、输出结果分开管理是后续排查问题和复现结果的基础。5. 模型加载与本地推理5.1 对比式 VLM图文相似度计算先用 CLIP 系列模型做图文相似度。下面的代码给出从加载模型到输出相似度的完整流程模型名按你实际下载的权重替换。import torch from transformers import CLIPModel, CLIPProcessor from PIL import Image model CLIPModel.from_pretrained(openai/clip-vit-base-patch32) processor CLIPProcessor.from_pretrained(openai/clip-vit-base-patch32) query 白色帆布鞋 男款 image Image.open(data/images/product_001.jpg) inputs processor(text[query], imagesimage, return_tensorspt, paddingTrue) with torch.no_grad(): outputs model(**inputs) # 图文余弦相似度范围约为 0 到 1 similarity outputs.logits_per_image.item() print(image-text similarity:, similarity)这一步适合验证“Query 文本和页面图片是否匹配”。要注意 CLIP 对中文的支持取决于训练数据英文原版权重对中文文本效果不稳定中文场景优先换中文或多语言版本。5.2 生成式大模型相关性判定与理由输出生成式多模态大模型负责输出带理由的判定。下面是一个通用模板具体模型类名、对话模板和图片输入方式以你选用的模型库为准不要直接照抄。import base64 import json import torch from PIL import Image # 假设你已经加载了支持图片输入的多模态模型 processor 和 model # 以 transformers 生态为例实际类名需要按模型卡片调整 # from transformers import AutoModelForVision2Seq, AutoProcessor # processor AutoProcessor.from_pretrained(your-vlm-model, trust_remote_codeTrue) # model AutoModelForVision2Seq.from_pretrained(your-vlm-model, torch_dtypetorch.float16, device_mapauto) def judge_relevance(query, title, body, image_path): image Image.open(image_path).convert(RGB) prompt ( 你是搜索相关性评估员。请判断 Query 与网页是否相关。\n fQuery: {query}\n f网页标题: {title}\n f网页正文摘要: {body}\n 请结合图片内容判断只输出 JSON\n {relevance: 0 或 1, score: 1-5, reason: 简要说明} ) # 把你的 inputs 构造方式替换到这里 # inputs processor(textprompt, imagesimage, return_tensorspt) # outputs model.generate(**inputs, max_new_tokens128, do_sampleFalse) # answer processor.decode(outputs[0], skip_special_tokensTrue) # return json.loads(answer) raise NotImplementedError(请替换为所选模型的实际推理代码)这段代码的关键点生成时把do_sample设成False也就是 greedy decoding避免同一输入多次打分波动输出格式强制 JSON方便后续解析和落库如果模型对 JSON 格式不稳可以在 prompt 末尾加一句“不要输出任何多余内容”。5.3 输出解析与分数归一生成式模型输出的 score 是 1 到 5 的离散分对比式 VLM 输出的是 0 到 1 的余弦相似度两者不能直接混用。建议在融合前各自做归一化生成式分数除以 5对比式分数按 min-max 统计映射到 0-1。归一化的统计量要从评估集计算不要随便拍一个固定值。6. 搭建相关性打分 API 服务6.1 FastAPI 单条打分接口把模型推理封装成 REST API方便接入标注平台、评测脚本或线上降级链路。下面是一个 FastAPI 服务模板模型初始化放在启动阶段避免每个请求重复加载。# app.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel import base64 from io import BytesIO from PIL import Image app FastAPI() class ScoreRequest(BaseModel): query: str title: str body: str image_base64: str # 可选Base64 编码后的图片 app.on_event(startup) def load_models(): # 在这里加载对比式 VLM 和生成式大模型保存到全局变量 pass app.post(/score) def score(req: ScoreRequest): if not req.query: raise HTTPException(status_code400, detailquery is required) image None if req.image_base64: image Image.open(BytesIO(base64.b64decode(req.image_base64))).convert(RGB) # 实际逻辑 # 1. 计算 S1 文本相似度 # 2. 计算 S2 图文相似度 # 3. 对边界样本调用生成式大模型得到 S3 与 reason # 4. 融合分数 return { query: req.query, score: 0.0, relevant: False, reason: }启动命令uvicorn app:app --host 127.0.0.1 --port 8000注意--host在生产环境不要暴露到公网建议绑定内网地址并在前面加一层鉴权或网关。6.2 curl 调用示例curl -X POST http://127.0.0.1:8000/score \ -H Content-Type: application/json \ -d { query: 白色帆布鞋 男款, title: 新款休闲鞋, body: 轻便透气适合日常穿搭, image_base64: }图片以 Base64 传输时单张图可能很大建议服务端限制请求体大小或者改用上传图片后返回图片 ID、再在打分请求里引用 ID 的方式减少重复传输。6.3 Python 批量调用import requests def score_one(query, title, body, image_base64): resp requests.post( http://127.0.0.1:8000/score, json{ query: query, title: title, body: body, image_base64: image_base64, }, timeout60, ) resp.raise_for_status() return resp.json() result score_one(白色帆布鞋 男款, 新款休闲鞋, 轻便透气) print(result)7. 批量评估任务与指标计算7.1 输入格式与批处理脚本批量评估时把待测样本写成 JSONL每行一条包含 id、query、title、body、image_path 和可选的 human_label。逐条读取、逐条打分、逐条写结果这样中途失败也不会丢已完成的数据。import json from concurrent.futures import ThreadPoolExecutor def process_line(line): # 调用 /score 接口或本地推理 # 这里需要替换成你的实际打分函数 return { id: line[id], pred_score: 0.0, pred_relevant: False, } def run_batch(input_path, output_path, max_workers4): with open(input_path, r, encodingutf-8) as f: items [json.loads(l) for l in f if l.strip()] with ThreadPoolExecutor(max_workersmax_workers) as pool: results list(pool.map(process_line, items)) with open(output_path, w, encodingutf-8) as f: for item, result in zip(items, results): merged {**item, **result} f.write(json.dumps(merged, ensure_asciiFalse) \n) if __name__ __main__: run_batch(data/queries.jsonl, outputs/pred_results.jsonl, max_workers4)并发数要按 GPU 显存调整。生成式大模型并发太高会 OOM稳妥做法是先跑一个 4 并发的小样本观察显存峰值再放开。7.2 NDCG 与一致性计算有 human_label 后可以用 NDCGk 评估排序质量。下面是标准的 NDCG 实现可以直接抄进脚本。import math def ndcg_at_k(relevances, k10): # relevances: 按模型排序后的人工标注分列表0 表示不相关越大越相关 dcg sum( (2 ** rel - 1) / math.log2(i 2) for i, rel in enumerate(relevances[:k]) if rel 0 ) ideal sorted(relevances, reverseTrue)[:k] idcg sum( (2 ** rel - 1) / math.log2(i 2) for i, rel in enumerate(ideal) if rel 0 ) return dcg / idcg if idcg 0 else 0.0除了 NDCG还要看模型打分和人工标注的相关系数比如 Spearman。相关系数低说明模型排序和人的直觉不一致即使 NDCG 还行也要检查是不是分数分布太集中。7.3 判断是否成功的标准一次批量评估跑完至少回答四个问题模型打分和人工标注在易分样本上是否一致边界样本集中在哪些类型每个 Query 的打分方差是否过大单条样本平均延迟和 P99 延迟是多少。如果延迟超预算即使准确率再好也不能上线需要先做量化或模型蒸馏。8. 资源占用与性能观察8.1 显存与延迟观察方法推理过程中用nvidia-smi或nvidia-smi -l 2实时查看显存占用。重点看两个值显存峰值和 GPU 利用率。显存峰值决定服务能跑多大的并发GPU 利用率决定资源有没有吃满。生成式大模型推理时如果利用率和显存都不稳定可能是显存碎片或并发设置不合理。nvidia-smi -l 2更细的指标可以用 PyTorch 的 profiler 或者 Nsight Systems 查看每层的耗时定位瓶颈在视觉编码、文本解码还是图像预处理。8.2 降低显存占用的常规手段第一FP16 推理大部分消费级显卡和服务器显卡都支持显存占用直接减半。第二INT8 或 4-bit 量化7B 模型可以压到 6GB 到 8GB 左右但量化后打分稳定性需要用评估集验证。第三用小 batch生成式模型对 batch 不敏感batch 大显存容易爆收益却不大。第四用 vLLM 这类推理框架做连续批处理吞吐比原生 transformers 的 generate 高很多适合批量任务。8.3 Web 规模下的成本控制思路Web 规模搜索不可能对全量候选跑生成式大模型这是硬约束。务实做法是分层对比式 VLM 处理全量候选做粗排生成式大模型只处理 top 200线上只部署对比式模型把生成式大模型用于离线评估、训练蒸馏模型和审核边界样本。这种设计下真实线上延迟主要由对比式 VLM 的编码和向量检索决定7B 大模型不在请求关键路径上。9. 常见问题与排查方法问题现象可能原因排查方式解决方案模型加载时 OOM显存不足或未开启量化nvidia-smi 查看显存占用换小模型、开启 FP16/INT8、使用 device_mapauto中文文本乱码编码或 tokenizer 配置问题打印输入输出日志检查字符编码统一 UTF-8检查 processor 的 tokenizer 是否匹配图片无法加载路径错误、图片损坏或格式不支持单测一张图打印异常堆栈增加重试、格式校验和损坏图片跳过逻辑打分结果不稳定提示词扰动或生成参数未固定同一输入多次调用看分数方差固定 promptdo_sampleFalsetemperature0生成式模型输出非法 JSON模型没按格式输出查看原始输出文本在 prompt 中强调“只输出 JSON”加解析容错API 请求超时生成式模型单条推理太慢查看服务日志统计单条耗时增大超时时间改用批量接口或者更小的模型批量任务卡住某个样本长期不结束或 worker 异常看 worker 日志和任务进度增加单任务超时、失败重试、断点续跑显存逐步上涨直至 OOM推理框架显存缓存未释放观察长时间运行的显存曲线重启服务或改用 vLLM 等带显存管理的框架最值得注意的是打分稳定性问题。很多人第一次跑生成式 VLM 打分会惊讶于同一输入两次结果不同。原因通常是采样参数没关或者 prompt 里夹带了随机感较强的表述。排查时先把do_sampleFalse、temperature0再跑三遍相同输入验证。10. 最佳实践与合规建议第一次搭建不要直接上全量数据。建议先用 200 到 500 条样本跑通“数据准备 - 模型打分 - 指标计算 - 人工复核”全流程确认每个环节的输出格式和错误处理都正确再逐步扩大数据量。模型文件、输入素材、输出结果分目录管理每条结果带模型版本、prompt 版本和数据版本否则后面很难复现“上周那个分数是哪次跑出来的”。批量任务必须加日志、超时和失败重试。推荐每个任务写一条 JSON 日志记录样本 id、耗时、返回码、原始输出和解析后结果排查时能直接定位到具体样本。API 服务要限制访问范围至少绑定内网地址加简单的 token 鉴权不给公网裸奔。合规方面有几个红线。网页图片通常有版权搭建评估数据集时要遵守来源网站的授权条款、robots 协议和平台规则不能随意抓取、打包、分发。涉及个人数据的 Query 或页面内容要去标识化处理并在测试环境验证。任何使用 VLM 做相关性判断的系统都不能把模型输出当作最终裁决尤其在高影响的场景里需要人工复核。多模态模型同样存在幻觉和偏见对敏感内容、争议内容、版权素材的判断必须做额外的安全过滤和人工审核。11. 总结与下一步VLM 做相关性度量最值得尝试的点是让打分链路第一次拥有了“看图”能力。最先应该验证的功能是找 50 个文本模型易错但看图就能判对的样本用对比式 VLM 和生成式大模型分别打分看能不能把这类样本捞回来。最容易踩的坑有两个一是直接对全量候选跑生成式大模型成本和延迟直接失控二是生成参数没固定导致打分不稳定后续所有指标都不可信。下一步的扩展方向可以分三条。第一条是把生成式大模型当标注器用它的输出训练一个小的蒸馏模型部署到线上打分链路。第二条是引入网页截图让模型直接看页面整体版式而不是只看首图这对信息流和本地生活场景帮助更大。第三条是把相关性度量和用户点击行为做联合建模用 VLM 的输出作为特征而不是替代整个排序模型。这套方案不适合追求“极致精度但不管成本”的场景也不适合纯文本语料。如果你的业务是电商、本地生活、信息流这类图片强相关的搜索场景建议先按本文的流程搭一个离线评估集跑完 500 条样本的对比实验再决定要不要进入线上链路。