Lychee Rerank MM快速上手:Jupyter Notebook交互式调试重排序Pipeline
你是否遇到过这样的问题:在多模态检索系统中,初筛结果明明包含正确答案,但排序靠后,用户根本看不到?传统双塔模型对图文语义的深层对齐能力有限,而端到端大模型又太重、难调试、无法细粒度观察决策过程。Lychee Rerank MM 就是为解决这个“最后一公里”问题而生——它不是另一个黑盒推理服务,而是一个可触摸、可拆解、可逐层验证的重排序调试环境。
本文不讲论文、不堆参数,只聚焦一件事:如何在 Jupyter Notebook 里真正“看见”重排序发生了什么。你会亲手加载模型、构造图文输入、拦截中间 logits、可视化 yes/no 概率变化、对比不同指令的影响——所有操作都在一个 notebook 里完成,无需启动 Web 服务,不依赖 Streamlit 界面。这不是部署指南,而是一份给工程师和算法同学的“显微镜使用手册”。
1. 为什么需要 Notebook 调试模式
1.1 Web 界面 vs Notebook:两种完全不同的工作流
Streamlit 界面很友好,适合演示和快速验证。但它隐藏了太多细节:
- 你不知道模型内部到底怎么处理一张图+一段文字;
- 无法查看
yes和notoken 的原始 logits 值; - 指令(instruction)微调带来的效果差异只能靠肉眼感受;
- 批量重排序时,你看到的是最终排序结果,却看不到每个文档的原始得分分布。
而 Jupyter Notebook 提供的是完全透明的控制权:
- 每一步 tensor 都可打印、可绘图、可保存;
- 可自由替换分词器、修改 prompt 模板、注入自定义前缀;
- 支持断点调试、变量检查、梯度追踪(即使只是推理);
- 所有代码即文档,下次复现实验只需重跑 notebook。
关键区别:Streamlit 是“成品交付”,Notebook 是“研发沙盒”。
1.2 本教程能帮你做到什么
- 在 5 分钟内完成本地环境初始化(无需 GPU 也可 CPU 演示)
- 加载 Qwen2.5-VL 模型并验证图文编码一致性
- 构造真实场景的 Query-Document 对(如:“这张图里的商品适合送母亲节吗?” + 商品详情图+文案)
- 获取并解析模型输出的 yes/no logits,计算归一化相关性得分
- 对比不同 instruction 对同一输入的影响(例如换用 “Is this passage relevant to the query?”)
- 将单次分析封装成函数,支持批量文档打分与排序
全程使用 Python 原生接口,不依赖任何私有 SDK 或封装库。
2. 环境准备与模型加载
2.1 最小依赖安装(CPU 友好版)
我们不强制要求 A100。以下命令可在 CPU 或任意 GPU 上运行(仅推理,速度慢但功能完整):
# 创建干净环境(推荐) python -m venv lychee_env source lychee_env/bin/activate # Linux/macOS # lychee_env\Scripts\activate # Windows # 安装核心依赖(避开 flash-attn 等 GPU 强依赖) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu pip install transformers==4.41.2 accelerate==0.30.2 pillow matplotlib seaborn pip install qwen-vl-utils==0.1.0 # 官方图像预处理工具注意:
qwen-vl-utils是 Qwen2.5-VL 官方维护的图像编码工具包,必须安装。它负责将 PIL 图像转为符合模型输入格式的 pixel values 和 image grid。
2.2 模型加载:三步走,拒绝黑盒
Lychee Rerank MM 的核心是 Qwen2.5-VL,但我们不直接调用pipeline()。我们要手动控制每一步:
- 加载分词器(Tokenizer):处理文本 + 插入图像占位符
- 加载视觉编码器(Vision Tower):将图像转为 patch embeddings
- 加载语言模型(LLM):融合图文特征,生成 yes/no 判断
from transformers import AutoTokenizer, AutoModelForCausalLM from qwen_vl_utils import process_vision_info import torch # Step 1: 加载 tokenizer(支持图文混合输入) tokenizer = AutoTokenizer.from_pretrained( "Qwen/Qwen2.5-VL-7B-Instruct", trust_remote_code=True ) tokenizer.pad_token_id = tokenizer.eos_token_id # 兼容 batch 推理 # Step 2: 加载模型(自动选择精度) model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen2.5-VL-7B-Instruct", torch_dtype=torch.bfloat16, # BF16,平衡精度与显存 device_map="auto", # 自动分配到 GPU/CPU trust_remote_code=True ) model.eval() # 进入推理模式验证是否加载成功:
print(f"模型设备: {next(model.parameters()).device}") print(f"总参数量: {sum(p.numel() for p in model.parameters()) / 1e9:.1f}B") # 输出应类似:模型设备: cuda:0,总参数量: 7.3B3. 构建多模态输入:从原始数据到模型张量
3.1 理解 Qwen2.5-VL 的输入结构
Qwen2.5-VL 不是简单拼接图文,而是采用“图文交错 tokenization”:
- 文本中用
<|image_pad|>占位符标记图像位置; - 图像被切分为多个 patch,每个 patch 映射为一个特殊 token;
- 模型内部通过 cross-attention 实现图文对齐。
因此,构造输入 ≠ 拼字符串。我们必须用qwen_vl_utils正确编码。
3.2 示例:电商场景下的 Query-Document 对
假设你要重排序一个搜索结果页:
- Query(图文混合):用户上传了一张“蓝色陶瓷马克杯”的图片,并输入文字:“适合送妈妈的杯子”
- Document(纯文本):商品标题 + 详情描述
from PIL import Image import requests from io import BytesIO # 模拟用户上传的图片(可替换为本地路径) img_url = "https://peppa-bolg.oss-cn-beijing.aliyuncs.com/cup_blue.jpg" response = requests.get(img_url) image = Image.open(BytesIO(response.content)).convert("RGB") # Query:图文混合(注意格式!) query_messages = [ { "role": "user", "content": [ {"type": "image", "image": image}, {"type": "text", "text": "适合送妈妈的杯子"} ] } ] # Document:纯文本(商品信息) document_text = "【母亲节限定】北欧风手工陶瓷马克杯,釉下彩工艺,容量350ml,防烫手柄,礼盒包装,支持刻字。" # 使用官方工具生成模型输入 from qwen_vl_utils import process_vision_info text_inputs, image_inputs = process_vision_info(query_messages) # Tokenize text(自动插入 <image> 占位符) inputs = tokenizer( text_inputs, return_tensors="pt", padding=True, truncation=True, max_length=2048 ) # 合并图像输入(关键!) if image_inputs: inputs["image"] = image_inputs[0].unsqueeze(0) # [1, C, H, W]关键点提醒:
process_vision_info()返回的image_inputs是预处理后的 tensor,不是原始 PIL;inputs["image"]必须和inputs["input_ids"]的 batch 维度对齐;- 如果 document 是纯文本,直接 tokenize 即可,无需图像字段。
4. 执行重排序推理与得分解析
4.1 核心逻辑:从 logits 到相关性得分
Lychee Rerank MM 的判断逻辑非常明确:
模型输出一个序列,我们只关心最后几个 token 中"yes"和"no"的 logits 值,然后做 softmax 归一化。
# 构造完整输入(Query + Document) # 指令模板(固定,保证行为一致) instruction = "Given a web search query, retrieve relevant passages that answer the query." full_prompt = f"<|im_start|>system\n{instruction}<|im_end|>\n<|im_start|>user\n{query_text}<|im_end|>\n<|im_start|>assistant\n" # 注意:此处 query_text 应已包含 <image> 占位符(由 process_vision_info 生成) inputs = tokenizer(full_prompt, return_tensors="pt", add_special_tokens=False) inputs["image"] = image_inputs[0].unsqueeze(0) # 推理(无梯度,节省显存) with torch.no_grad(): outputs = model(**inputs) # 获取最后 token 的 logits(分类头输出) last_logits = outputs.logits[0, -1, :] # [vocab_size] # 查找 yes/no token id yes_id = tokenizer.convert_tokens_to_ids("yes") no_id = tokenizer.convert_tokens_to_ids("no") # 提取并归一化 yes_logit = last_logits[yes_id].item() no_logit = last_logits[no_id].item() import math # softmax 归一化到 [0,1] score = 1 / (1 + math.exp(no_logit - yes_logit)) # 等价于 exp(yes)/(exp(yes)+exp(no)) print(f"yes logit: {yes_logit:.3f}, no logit: {no_logit:.3f}") print(f"相关性得分: {score:.3f}") # 例如:0.872 → 高相关输出示例:
yes logit: 4.218, no logit: 1.053 相关性得分: 0.9624.2 可视化:一眼看懂模型“思考过程”
用matplotlib绘制 yes/no logits 分布,比数字更直观:
import matplotlib.pyplot as plt plt.figure(figsize=(6, 2)) plt.bar(["yes", "no"], [yes_logit, no_logit], color=["#28a745", "#dc3545"]) plt.title("模型对 Query-Document 的原始判断 logits") plt.ylabel("Logit value") for i, v in enumerate([yes_logit, no_logit]): plt.text(i, v + 0.1, f"{v:.2f}", ha="center") plt.show()这张图就是你的“决策仪表盘”。如果
yes和nologit 差距很小(比如都接近 0),说明模型拿不准——这时你就该检查输入质量或 instruction 是否清晰。
5. 批量重排序实战:从单条到列表排序
5.1 封装为可复用函数
把上述逻辑打包,支持传入 Query(图文)和 Documents 列表(纯文本):
def rerank_documents( model, tokenizer, query_messages, documents, instruction="Given a web search query, retrieve relevant passages that answer the query.", device="cuda" if torch.cuda.is_available() else "cpu" ): scores = [] # 预处理 query(一次即可) text_inputs, image_inputs = process_vision_info(query_messages) if image_inputs: image_tensor = image_inputs[0].unsqueeze(0).to(device) for doc in documents: # 构造完整 prompt full_prompt = ( f"<|im_start|>system\n{instruction}<|im_end|>\n" f"<|im_start|>user\n{text_inputs}\n{doc}<|im_end|>\n" f"<|im_start|>assistant\n" ) inputs = tokenizer( full_prompt, return_tensors="pt", truncation=True, max_length=2048 ).to(device) if image_inputs: inputs["image"] = image_tensor with torch.no_grad(): outputs = model(**inputs) last_logits = outputs.logits[0, -1, :] yes_id = tokenizer.convert_tokens_to_ids("yes") no_id = tokenizer.convert_tokens_to_ids("no") score = 1 / (1 + math.exp(last_logits[no_id] - last_logits[yes_id])) scores.append(score) # 返回按得分降序排列的 (document, score) 元组 return sorted(zip(documents, scores), key=lambda x: x[1], reverse=True) # 使用示例 docs = [ "【母亲节限定】北欧风手工陶瓷马克杯...", "【办公必备】不锈钢保温杯,500ml大容量...", "【儿童专用】硅胶软边儿童水杯,防摔设计..." ] results = rerank_documents(model, tokenizer, query_messages, docs) for i, (doc, score) in enumerate(results, 1): print(f"{i}. 得分 {score:.3f} | {doc[:50]}...")输出:
1. 得分 0.962 | 【母亲节限定】北欧风手工陶瓷马克杯,釉下彩... 2. 得分 0.315 | 【办公必备】不锈钢保温杯,500ml大容量... 3. 得分 0.102 | 【儿童专用】硅胶软边儿童水杯,防摔设计...5.2 调试技巧:快速定位 bad case
当某个文档得分异常低,别急着调参。先做三件事:
- 打印原始 prompt:确认
<image>占位符是否在正确位置; - 检查 tokenizer 输出长度:
len(inputs["input_ids"][0]),超长会被截断; - 手动测试 yes/no token id:
tokenizer.decode([yes_id, no_id]),确认没映射错。
这些在 notebook 里一行命令就能完成,远比改配置重启服务快得多。
6. 进阶技巧:让重排序更可控
6.1 Instruction 敏感性实验
同一 Query-Document 对,换不同 instruction,得分可能差 0.3+:
| Instruction | 示例得分 |
|---|---|
"Given a web search query..." | 0.962 |
"Is this passage relevant to the query?" | 0.821 |
"Does this text answer the user's question?" | 0.895 |
在 notebook 中一键对比:
instructions = [ "Given a web search query, retrieve relevant passages that answer the query.", "Is this passage relevant to the query?", "Does this text answer the user's question?" ] for inst in instructions: s = rerank_documents(model, tokenizer, query_messages, [document_text], inst)[0][1] print(f"'{inst[:30]}...': {s:.3f}")建议:在生产环境中固定使用第一条(官方推荐),但在调试阶段,多试几种是快速理解模型 bias 的捷径。
6.2 显存优化:CPU 模式也能跑通
如果你只有 CPU,只需两处修改:
# 加载模型时 model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen2.5-VL-7B-Instruct", torch_dtype=torch.float32, # 改为 float32(CPU 不支持 bfloat16) device_map="cpu", trust_remote_code=True ) # 推理时关闭 bfloat16 with torch.no_grad(): outputs = model(**inputs) # 自动在 CPU 上运行虽然慢(单次约 40 秒),但功能 100% 一致,适合算法验证和教学演示。
7. 总结:你刚刚掌握的不只是一个工具
7.1 重排序调试的核心能力清单
- 你学会了如何绕过 Web 界面,在 notebook 中直连模型底层,获取原始 logits;
- 你掌握了多模态输入的正确构造方式,避免因格式错误导致的“模型不 work”假象;
- 你拥有了批量重排序的可复用函数,可直接集成进现有检索 pipeline;
- 你建立了对 instruction 敏感性的实证认知,不再盲目相信“默认值”;
- 你获得了 CPU 可运行的最小验证路径,降低团队协作门槛。
7.2 下一步行动建议
- 把本 notebook 作为团队内部“重排序标准验证模板”,统一调试流程;
- 将
rerank_documents()函数封装为 REST API,供线上服务调用; - 基于 yes/no logits 分布,构建自动 fallback 机制(如得分 < 0.4 时触发规则引擎);
- 尝试替换 vision tower(如用 SigLIP),观察图文对齐能力变化。
重排序不是终点,而是让多模态检索真正“可解释、可优化、可信任”的起点。而这一切,从你运行完第一个 cell 开始。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。